2024年9月24日

WeakMap と WeakSet

チャプター ガベージコレクション で学んだ通り、JavaScriptエンジンは、それが到達可能な(そして潜在的に利用される可能性がある)間、メモリ上に値を保持しています。

例:

let john = { name: "John" };

// オブジェクトへはアクセス可能です。john がその参照を持っています

// 参照を上書きします
john = null;

// オブジェクトはメモリから削除されるでしょう

通常、オブジェクトまたは配列の要素、もしくは別のデータ構造のプロパティは到達可能と考えられ、そのデータ構造がメモリにいる間は保持され続けます。

例えば、あるオブジェクトを配列に入れた場合、その配列が生きている間は、他の参照がなくてもそのオブジェクトは生きていることになります。

例:

let john = { name: "John" };

let array = [ john ];

john = null; // 参照を上書きします

// 以前 john で参照されていたオブジェクトは配列内に格納されています
// そのため、ガベージコレクションされません。
// array[0] で取得することが可能です

また、通常の Map のキーとしてオブジェクトを使うと、Map が存在している間はそのオブジェクトも存在します。これはメモリを占め、ガベージコレクションされないかもしれません。

例:

let john = { name: "John" };

let map = new Map();
map.set(john, "...");

john = null; // 参照を上書きします

// john は map の中に保持されています
// map.keys() で取得することができます

WeakMap はこの点で根本的に異なります。これらはキーオブジェクトのガベージコレクションを妨げることはありません。

例でその意味するところを見ていきましょう。

WeakMap

Map との最初の違いは、WeakMap のキーはプリミティブな値ではなくオブジェクトでなければならないことです:

let weakMap = new WeakMap();

let obj = {};

weakMap.set(obj, "ok"); // 正常に動作します (オブジェクトのキー)

// キーに文字列は使えません
weakMap.set("test", "Whoops"); // エラー, "test" はオブジェクトではないため

いま、オブジェクトをキーとして使用し、そのオブジェクトへの参照が他にない場合、自動的にメモリ(と map)から削除されます。

let john = { name: "John" };

let weakMap = new WeakMap();
weakMap.set(john, "...");

john = null; // 参照を上書きします

// john はメモリから削除されます!

上の例を通常の Map の場合と比べて見てください。WeakMap のキーとしてのみ john が存在する場合、自動的に削除されます。

WeakMap は繰り返しと、メソッド keys(), values(), entries() をサポートしません。そのため、すべてのキーや値を取得する方法はありません。

WeakMap は次のメソッドのみを持っています:

  • weakMap.get(key)
  • weakMap.set(key, value)
  • weakMap.delete(key, value)
  • weakMap.has(key)

なぜこのような制限があるのでしょうか?これは技術的な理由です。もしオブジェクトがすべての他の参照を失った場合(上のコードの john のように)、自動的に削除されます。しかし、技術的には いつクリーンアップが発生するか は正確には指定されていません。

それはJavaScriptエンジンが決定します。エンジンはすぐにメモリのクリーンアップを実行するか、待ってより多くの削除が発生した後にクリーンアップするかを選択できます。従って、技術的にはWeakMap の現在の要素数はわかりません。エンジンがクリーンアップしている/していない、または部分的にそれをしているかもしれません。このような理由から、WeakMap