まず「GC」とは何か(やさしくイメージから)
JavaScript の世界では、
「もう使われなくなったデータを、自動で片付けてくれる仕組み」 が動いています。
これを GC(Garbage Collection / ガベージコレクション)と呼びます。
たとえば:
let user = { name: "Alice" };
user = null; // もうこのオブジェクトを指している変数はない
JavaScript{ name: "Alice" } というオブジェクトは、
どこからも参照されていない「ゴミ」になります。
すると、JavaScript エンジンの GC が「こいつ、もう誰からも使われてないな」と判断して、
メモリから自動的に回収してくれます。
ここが重要です。
「どこからも参照されていないもの」は、GC によって勝手に消える。
開発者が delete や free みたいなことをする必要はありません。
Strong な参照と Weak な参照の違い
Strong(強い)参照とは
普通に変数に代入したり、配列やオブジェクトの中に入れたりする参照は、
全部「強い参照」です。
const obj = { id: 1 }; // ここで強い参照
const arr = [obj]; // 配列の中からも強い参照
const holder = { value: obj }; // プロパティからも強い参照
JavaScriptどこか 1 つでも「強い参照」が残っている限り、
そのオブジェクトは GC によって消されません。
たとえ「もう使わないつもり」でも、
どこかの変数や構造が参照し続けていると、メモリには残り続けます。
これが「メモリリーク」の原因になります。
Weak(弱い)参照とは
Weak な参照は、
「このオブジェクト、ここからは参照してるけど、もし他から参照がなくなったら、遠慮なく GC で消していいよ」
という性質を持っています。
ES6 の世界で「Weak」という名前がついているのは、
WeakMapWeakSet
の 2 つです。
これらの構造は、
「中にオブジェクトを入れておいても、それが 唯一の 参照だとしたら、GC が普通に回収してしまう」
という性質を持っています。
つまり:
- Strong な構造(配列、オブジェクト、Map、Set):
参照がある限り GC されない - Weak な構造(WeakMap、WeakSet):
「他からの参照がなくなったオブジェクト」は、
WeakMap / WeakSet の中にあっても GC されてよい
ここが重要です。
Weak な構造は、「オブジェクトの寿命を一緒に伸ばさないための構造」。
「つい保持し続けて、メモリリークの原因になる」のを避けるために存在します。
WeakMap と GC(具体的なイメージ)
WeakMap の基本
WeakMap は「キーをオブジェクトに限定した、弱い Map」です。
const wm = new WeakMap();
let obj = { id: 1 };
wm.set(obj, "追加情報");
JavaScriptここで wm は obj をキーとして何かを覚えています。
でも、これは「弱い参照」です。
obj = null; // これで { id: 1 } への強い参照がなくなった
JavaScriptどこにも { id: 1 } への強い参照が残っていなければ、
GC はこのオブジェクトを回収してOKです。
そのとき、WeakMap の中の entry(キーと値のペア)も、
一緒に「なかったこと」にされます。
なぜこれがうれしいのか
もし同じことを「普通の Map」でやるとどうなるでしょう?
const m = new Map();
let obj = { id: 1 };
m.set(obj, "追加情報");
obj = null;
JavaScriptここでも obj = null; としていますが、m の中からは依然として { id: 1 } を参照しているので、
GC はこのオブジェクトを「まだ使われている」と判断します。
つまり、Map に入れている限り、
そのオブジェクトは生き続けてしまうわけです(メモリリークの可能性)。
一方、WeakMap なら「他の参照がなくなれば、WeakMap の中にいても GC される」ため、
そのオブジェクトと紐づいていた値も自動的に消えてくれます。
ここが重要です。
WeakMap は、「オブジェクトに追加情報を紐づけたいけれど、そのオブジェクトの寿命を伸ばしたくはない」場面で使う。
だからこそ GC とセットで理解する必要があります。
WeakSet と GC(同じ考え方)
WeakSet の基本
WeakSet は「オブジェクトだけを要素に持つ弱い Set」です。
const ws = new WeakSet();
let obj = { id: 1 };
ws.add(obj); // obj がメンバー
obj = null; // 他からの参照がなくなると
// GC によって { id: 1 } は回収され、WeakSet からも自動的に消える
JavaScriptWeakSet に入れたオブジェクトも、
他に強い参照がなければ GC の対象になります。
どんなときに使うか(イメージ)
例えば、「あるオブジェクトが既に処理済みかどうか」を覚えておきたいとします。
const processed = new WeakSet();
function process(obj) {
if (processed.has(obj)) {
console.log("もう処理済みです");
return;
}
console.log("初回処理をします");
processed.add(obj);
}
JavaScriptこのとき、obj が他から参照されなくなれば、processed の中の記録も GC によっていつか消えます。
もしこれを普通の Set でやると、
Set がオブジェクトを参照し続けるので、
オブジェクトは永遠にメモリに残り続けるかもしれません。
Weak 構造の「できないこと」=GC と仲良しな代償
ループできない・size もない
WeakMap / WeakSet は、GC と連携するために
「いつ要素が消えるか分からない」前提で設計されています。
そのため、
for...ofで全部なめるkeys()/values()/entries()を呼ぶsizeで個数を数える
といった操作は、一切できません。
const wm = new WeakMap();
const ws = new WeakSet();
// これらは全部 NG(メソッドが存在しない or エラー)
// for (const v of ws) {}
// ws.size
// wm.keys()
// wm.forEach(...)
JavaScriptなぜかというと、
「要素の数や一覧を、信頼できる形で提供する」
ということをしてしまうと、
「GC がいつ動くか分からない」という性質と矛盾してしまうからです。
ここが重要です。
Weak 構造は「中身を一覧したり、個数を数えたりするためのものではない」。
あくまで「既知のオブジェクトに対して、付随情報やフラグをくっつけるためのもの」
と理解しておくと、迷いが減ります。
「普通の辞書や集合」としては向いていない
「キーにオブジェクトを使いたいから WeakMap」
「オブジェクトの集合が欲しいから WeakSet」
と安直に選ぶと、すぐに「size がない」「ループできない」といった不便さにぶつかります。
「中身を全部見たい」「何件あるか数えたい」「順番に処理したい」というニーズがあるなら、
普通の Map / Set を使うべきです。
Weak な構造は、「GC と仲良くしたい」特殊な用途にだけ使うものだと思ってください。
具体例で GC と Weak 構造の関係を体感する
例1:DOM 要素に追加情報を紐づける(WeakMap)
const elementData = new WeakMap();
function setElementData(el, data) {
elementData.set(el, data);
}
function getElementData(el) {
return elementData.get(el);
}
// どこかで
const div = document.createElement("div");
setElementData(div, { clicked: 0 });
div.addEventListener("click", () => {
const info = getElementData(div);
info.clicked++;
console.log("クリック回数:", info.clicked);
});
// 後で DOM を削除
document.body.removeChild(div);
// div への他の参照がなければ、
// div も、それに紐づく elementData 内のデータも GC によって回収される
JavaScriptここで WeakMap ではなく Map を使うと、
「DOM は削除されたのに、Map が参照を持ち続けていて GC されない」
というメモリリークが起こり得ます。
例2:循環参照をめぐる訪問済みフラグ(WeakSet)
function printObject(obj, visited = new WeakSet()) {
if (visited.has(obj)) {
console.log("[循環参照]");
return;
}
visited.add(obj);
for (const key in obj) {
const value = obj[key];
if (typeof value === "object" && value !== null) {
printObject(value, visited);
} else {
console.log(key, ":", value);
}
}
}
JavaScriptvisited に「訪問済みのオブジェクト」を入れておけば、
循環参照があっても無限ループを防げます。
この visited が WeakSet であることで、
探索が終わったあと、訪問済み情報が自然に GC の対象になります。
まとめ:GC と Weak 構造の関係を一言でいうと
GC(ガベージコレクション)は、
「どこからも参照されなくなったオブジェクトを、自動的に片付ける仕組み」。
Strong な構造(配列 / オブジェクト / Map / Set)は、
参照している限りオブジェクトを生かし続けるので、
油断するとメモリリークの原因になります。
Weak な構造(WeakMap / WeakSet)は、
オブジェクトに「弱い紐づけ」で情報を付けるための道具で、
そのオブジェクトが他から参照されなくなれば、
弱い紐づけごと GC によって回収されます。
押さえておきたいポイントを整理すると:
WeakMap / WeakSet は「GC によって中身が勝手に消えてよい」前提で設計されている
その代わり、size や for...of など、「全部を見る」機能は提供されない
「オブジェクトに追加情報やフラグを付けたいが、そのせいで寿命を伸ばしたくない」ときに使う
普通の辞書・集合として使いたいなら Map / Set を選ぶ
初心者のうちは、
「まずは Map / Set をちゃんと使いこなす」
「メモリリークや GC に意識が向き始めたら WeakMap / WeakSet を思い出す」
くらいの距離感でちょうどいいです。
そのときは、
「Strong な参照はオブジェクトを生かし続ける」
「Weak な参照は、他に誰もいなければ GC によって消される」
というこの 2 本柱を思い出して、
小さいサンプルで実験してみてください。
GC と Weak 構造の関係が、感覚としてスッと腑に落ちてくるはずです。
