JavaScript 速習チュートリアル

JavaScript ウィークマップ

1. WeakMap オブジェクトの基本

JavaScript の WeakMap は、キーと値のペアを保持するコレクションですが、キーは必ずオブジェクトである必要があります。
WeakMap の最大の特徴は、そのキーに対して「弱参照 (Weak Reference)」を保持する点にあります。

// WeakMap を作成
let myMap = new WeakMap();

// オブジェクトを作成
let myObj = {fname: "太郎", lname: "山田"};

// WeakMap に値をセット
myMap.set(myObj, "プレイヤー");

// WeakMap から値を取得
let type = myMap.get(myObj);

2. ガベージコレクション (Garbage Collection)

JavaScript は、ガベージコレクション(Garbage Collection) と呼ばれるメモリ管理メカニズムを採用しています。
その主な役割は以下の通りです:

  • メモリリソースの効率的な利用を確保する
  • 使用されなくなった変数が占有しているメモリを回収する
  • メモリリーク (Memory Leak) を防止する

3. 弱参照 (Weak References) の特徴

通常の Map とは異なり、WeakMap はそのキーがガベージコレクションされるのを妨げません。

プログラム内でそのオブジェクト(キー)への参照が他になくなった場合、そのオブジェクトはガベージコレクションの対象となります。
オブジェクトがメモリから回収されると、そのキーに関連付けられていたペアも自動的に WeakMap から削除されます。

let myMap = new WeakMap();
let myObj = {fname: "太郎", lname: "山田"};

myMap.set(myObj, "シークレット");

// オブジェクトへの参照を解除
myObj = null;

// これにより、myMap 内の myObj(およびその値)はガベージコレクションの対象となります

4. キーに関する制限

WeakMap のキーとして、プリミティブ値 (Primitive values) を使用することはできません。
キーは必ずオブジェクト、または登録されていない Symbol である必要があります。

この制限はガベージコレクションの仕組みに直結しています。プリミティブ値は、オブジェクトのようにガベージコレクションによって回収される対象ではないためです。

5. オブジェクトの効率的な追跡

WeakMap のエントリは弱く保持されています。オブジェクトであるキーが「到達不能(unreachable)」になると、そのマッピングは自動的に削除されます。
これは、ガベージコレクションを妨げることなく、オブジェクトに関するメタデータを追跡するのに最適です。

5.1 訪問者の追跡

let text = "";

// 訪問回数を保存するための WeakMap を作成
const visitsCount = new WeakMap();

// 訪問者オブジェクトを作成
const John = {name: "ジョン", age: 40};
const Paul = {name: "ポール", age: 41};
const Ringo = {name: "リンゴ", age: 42};
const George = {name: "ジョージ", age: 43};

// 訪問を追跡
track(Paul);
track(Ringo);
track(Paul);
track(Paul);
track(John);

// 訪問者を追跡するファンクション
function track(visitor) {
  let count = visitsCount.get(visitor) || 0;
  count++;
  visitsCount.set(visitor, count);
  text += visitor.name + " さん(" + visitor.age + "歳)は " + count + " 回目の訪問です。<br>";
}

5.2 自動クリーンアップ

訪問者オブジェクトへのすべての参照を削除すると、自動的にクリーンアップが行われます。

John = null;

// これで、John に関するエントリは WeakMap から自動的に削除されます

6. 反復処理の不可

WeakMap は 列挙可能(Enumerable)ではありません。

  • for ループ、forEach()keys() などを使用して、キーや値を反復処理(Iteration)することはできません。
  • 要素数を確認することもできません。WeakMap には size プロパティが存在しません。

7. 限定されたメソッド一覧

WeakMap で提供されているメソッドは非常に限定的です:

メソッド説明
new WeakMap()新しい WeakMap オブジェクトを作成します
get(key)キーに対応する値を取得します
set(key, value)キーと値をセットします
delete(key)キーで指定された要素を削除します
has(key)キーが存在する場合に true を返します

8. WeakMap による秘匿データ管理

WeakMap を利用して、外部からアクセスできないデータをオブジェクトに関連付けることができます。

// WeakMap を作成
const myMap = new WeakMap();

// プライベートフィールドのシミュレーション
class User {
  constructor(name) {
    myMap.set(this, {secret: "隠しデータ"});
    this.name = name;
  }
  
  getSecret() {
    return myMap.get(this).secret;
  }
}

const user1 = new User("ジョン");
let secret = user1.getSecret();

8.1 例の解説

WeakMap は反復処理を許可しません。そのため、外部のコードは WeakMap の中にどのオブジェクトが格納されているかを「発見」することができません。
シークレットデータを取得するには、コンストラクタ内で使用された this の参照が必要です。

外部コードは user1myMap にアクセスできますが、getSecret() のようなメソッドを明示的に公開しない限り、WeakMap 内部の this 参照にアクセスすることはできず、シークレット値は保護されます。

8.2 プライバシー

WeakMap は意図的にプライバシーを重視して設計されています。オブジェクトをキーとして set, get, has, delete を行うことはできますが、中身を検査(インスペクション)することはできません。

これは、JavaScript に正式な #private フィールドが導入される前に、クラスのプライベートプロパティをシミュレートするための優れたツールとして活用されてきました。