メモリ参照とは何か
JavaScript の配列・オブジェクトは「参照型」です。ここが重要です:変数には“値そのもの”ではなく“値への参照(ポインタのようなもの)”が入ります。参照が同じだと、片方を変更したときもう片方も変わって見えます。これが「共有参照」で、初心者が最初に踏みがちな落とし穴です。
const a = { n: 1 };
const b = a; // a と b は“同じオブジェクト”を参照
b.n = 2;
console.log(a.n); // 2(bの変更がaにも見える)
JavaScript浅いコピーと深いコピー(どこまで独立させるか)
浅いコピー(外側だけ新しく、内側は共有)
スプレッドや Object.assign は“1階層目”だけ新しくします。ネスト内は参照共有のままです。
const orig = { user: { name: "Alice" }, tags: ["js"] };
const shallow = { ...orig }; // 浅いコピー
shallow.user.name = "Bob";
console.log(orig.user.name); // "Bob"(内側は共有だった)
JavaScriptここが重要です:浅いコピーは「外側の箱を新しくするだけ」。ネストに入ると同じ中身を指します。
深いコピー(最深部まで完全に独立)
structuredClone はネスト全体を複製します。元とコピーを完全に切り離せます。
const orig = { user: { name: "Alice" }, tags: ["js"] };
const deep = structuredClone(orig); // 深いコピー
deep.user.name = "Carol";
console.log(orig.user.name); // "Alice"(独立)
JavaScriptここが重要です:深いコピーは安全ですが、重い処理です。常時使うのではなく「本当に元と切り離したい場面」に限定するのが設計のコツです。
共有参照によるバグ(エイリアシング問題)
“別名”で同じものを触ってしまう
別変数・別モジュール・別関数から“同じ実体”に触ると予期せぬ影響が出ます。これがエイリアシング(別名)問題。
function addTag(item, tag) {
item.tags.push(tag); // 破壊的(他所でも影響)
}
const a = { tags: ["a"] };
const b = a;
addTag(b, "b");
console.log(a.tags); // ["a","b"](aにも反映)
JavaScriptここが重要です:関数は“引数を壊さない”契約にする(非破壊更新)と、参照共有の副作用を抑えられます。
function addTagSafe(item, tag) {
return { ...item, tags: [...(item.tags ?? []), tag] };
}
JavaScript値の同一性と等価(=== と “同じ中身”の違い)
同一性(参照が同じか)と値等価(中身が同じか)
=== は「同じ参照か」を見ます。中身が同じでも“別インスタンス”なら === は false です。
const x = { n: 1 };
const y = { n: 1 };
console.log(x === y); // false(別インスタンス)
const z = x;
console.log(x === z); // true(同じ参照)
JavaScriptここが重要です:差分検知(UI)では“参照が変わったか”が有効です。中身の比較は重いので、基本は“必要箇所だけ新インスタンス”にして同一性の変化で検知します。
安全な更新の型(必要階層だけコピーする)
ネストの部分更新(階層ごとに新しくする)
更新対象の階層だけスプレッドで再構築し、その他の参照は保つと効率と安全のバランスが取れます。
const state = { user: { profile: { city: "Tokyo" }, flags: { vip: false } } };
const next = {
...state,
user: {
...state.user,
flags: { ...state.user.flags, vip: true } // ここだけ更新
}
};
JavaScriptここが重要です:一段でもコピーを省くと参照共有が残ります。必要な階層“だけ”新しくするのがコツ。
パフォーマンス視点の参照管理(コピーしすぎない)
毎回全体を深コピーしない
深コピーは重く、メモリも増えます。変更が必要な部分のみ再構築し、残りは参照を保つことで速度・メモリを節約します。
// 変更箇所だけ再構築して他は参照維持
const next = {
...state,
prefs: { ...state.prefs, theme: "dark" }
};
JavaScript中間配列を減らす(1パスで出力を作る)
filter→map のチェーンは中間配列を生みます。大規模では 1 回の走査で結果だけ作ると GC の負荷が減ります。
function filterMap(arr, pred, mapFn) {
const out = [];
for (const x of arr) if (pred(x)) out.push(mapFn(x));
return out;
}
JavaScript参照を前提にした設計(辞書化・正規化)
検索多発は辞書化して参照一本化
配列から辞書(ID→レコード)を作ると、更新は一点集中・参照は O(1) になります。表示時に必要なら配列化。
const dict = Object.fromEntries(list.map(x => [String(x.id), x]));
const nextDict = { ...dict, ["123"]: { ...dict["123"], active: true } };
const nextList = Object.values(nextDict);
JavaScriptここが重要です:同じレコードを複数箇所に持たず、“唯一の更新源”に集約すると参照の不整合が起きにくい。
参照とガベージコレクション(メモリリーク防止)
参照が残る限りメモリは解放されない
不要になったオブジェクトへの参照を持ち続けると GC されず、メモリリークになります。長寿コレクション(キャッシュ・グローバル配列)に“古い参照”を残さない設計が必要です。
// 悪い例:使い終わったら辞書から消さない
cache["old"] = bigObject; // ずっと参照が残る
// 良い例:期限や条件で明示的に参照を切る
delete cache["old"]; // 参照がなくなれば GC 対象になる
JavaScriptWeakMap / WeakSet で“弱い参照”を使う
キーが GC で消えると自動的に解放される“弱い参照”は、付随情報のメモ化や訪問済み管理に向きます。
const seen = new WeakSet();
function walk(obj) {
if (obj == null || typeof obj !== "object") return;
if (seen.has(obj)) return;
seen.add(obj);
for (const v of Object.values(obj)) walk(v);
}
JavaScriptここが重要です:WeakMap/WeakSet は“キーがオブジェクトに限る”“列挙できない”という制約がありますが、リーク防止に強い味方です。
クロージャと参照(関数が“環境”を持つ)
閉じ込めた参照は生き続ける
関数が外のオブジェクトをキャプチャすると、その関数が生きている限り参照も生きます。大きなデータを不用意に閉じ込めない。
function makeCounter(state) {
let count = 0; // 小さい環境なら安全
return () => ++count;
}
JavaScriptここが重要です:イベントリスナーなど長寿命の関数に“大きい配列やDOM”を閉じ込めるとリークの原因になります。必要データだけ渡す、解除する(removeEventListener)などの運用が大切です。
等価チェックとキャッシュ(参照で速くする)
参照同一性でメモ化・差分検知
参照が変わらなければ「内容が変わっていない」とみなせる設計にすると、高速な === 比較で再計算・再描画を省けます。
const cache = new Map();
function expensive(input) {
const hit = cache.get(input);
if (hit) return hit;
const out = compute(input);
cache.set(input, out);
return out;
}
JavaScriptここが重要です:非破壊更新で“変わった所だけ新インスタンス”にすると、この戦略が機能します。
まとめ
メモリ参照の核心は「配列・オブジェクトは参照型で、共有参照が副作用の源」という理解です。浅いコピーは内側が共有、深いコピーは安全だが重い。更新は“必要階層だけコピー”し、辞書化で唯一の更新源に集約、filter→map は 1 パスで中間配列を減らす。不要参照は切って GC を促し、弱い参照(WeakMap/WeakSet)で長寿構造のリークを防ぐ。参照同一性を活かした差分検知・メモ化を設計に織り込めば、初心者でも安全で速いメモリ運用ができます。
