JavaScript for impatient programmers (ES2022 edition)
この本のサポートをお願いします:購入または寄付
(広告です。ブロックしないでください。)

34 WeakMap(WeakMap)(上級)



WeakMapはMapに似ていますが、以下の点が異なります。

次の2つのセクションで、それが何を意味するのかを詳しく見ていきます。

34.1 WeakMapはブラックボックス

WeakMapの内部を検査することはできません。

これらの制限により、セキュリティプロパティが有効になります。Mark Miller氏の言葉を引用します。

weakmap/キーペア値からのマッピングは、weakmapとキーの両方を持っている人だけが観察または影響を与えることができます。 `clear()`を使用すると、WeakMapのみを持っている人がWeakMapとキーと値のマッピングに影響を与えることができてしまいます。

34.2 WeakMapのキーは*弱参照*される

WeakMapのキーは*弱参照*されると言われています。通常、あるオブジェクトが別のオブジェクトを参照している場合、前者が存在する限り、後者のオブジェクトはガベージコレクションされません。WeakMapでは、それは異なります。オブジェクトがキーであり、他の場所から参照されていない場合、WeakMapがまだ存在していてもガベージコレクションされる可能性があります。これにより、対応するエントリも削除されます(ただし、それを観察する方法はありません)。

34.2.1 すべてのWeakMapキーはオブジェクトでなければならない

すべてのWeakMapキーはオブジェクトでなければなりません。プリミティブ値を使用するとエラーが発生します。

> const wm = new WeakMap();
> wm.set(123, 'test')
TypeError: Invalid value used as weak map key

プリミティブ値をキーとして使用すると、WeakMapはブラックボックスではなくなります。しかし、プリミティブ値はガベージコレクションされないため、弱参照キーのメリットはありません。通常のMapを使用することもできます。

34.2.2 ユースケース:オブジェクトへの値の付加

これはWeakMapの主なユースケースです。WeakMapを使用して、オブジェクトに外部から値を添付できます。たとえば、

const wm = new WeakMap();
{
  const obj = {};
  wm.set(obj, 'attachedValue'); // (A)
}
// (B)

A行では、`obj`に値を添付しています。B行では、`wm`がまだ存在していても、`obj`は既にガベージコレクションされる可能性があります。オブジェクトに値を添付するこの手法は、そのオブジェクトのプロパティが外部に格納されていることと同じです。 `wm`がプロパティである場合、上記のコードは次のようになります。

{
  const obj = {};
  obj.wm = 'attachedValue';
}

34.3 例

34.3.1 WeakMapによる計算結果のキャッシュ

WeakMapを使用すると、メモリ管理を気にすることなく、以前に計算された結果をオブジェクトに関連付けることができます。次の関数`countOwnKeys()`は例です。以前の結果をWeakMap `cache`にキャッシュします。

const cache = new WeakMap();
function countOwnKeys(obj) {
  if (cache.has(obj)) {
    return [cache.get(obj), 'cached'];
  } else {
    const count = Object.keys(obj).length;
    cache.set(obj, count);
    return [count, 'computed'];
  }
}

この関数をオブジェクト`obj`で使用すると、結果は最初の呼び出しに対してのみ計算され、2回目の呼び出しではキャッシュされた値が使用されることがわかります。

> const obj = { foo: 1, bar: 2};
> countOwnKeys(obj)
[2, 'computed']
> countOwnKeys(obj)
[2, 'cached']

34.3.2 WeakMapでのプライベートデータの保持

次のコードでは、WeakMap `_counter`と`_action`を使用して、`Countdown`インスタンスの仮想プロパティの値を格納します。

const _counter = new WeakMap();
const _action = new WeakMap();

class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  dec() {
    let counter = _counter.get(this);
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}

// The two pseudo-properties are truly private:
assert.deepEqual(
  Object.keys(new Countdown()),
  []);

`Countdown`は次のように使用します。

let invoked = false;

const cd = new Countdown(3, () => invoked = true);

cd.dec(); assert.equal(invoked, false);
cd.dec(); assert.equal(invoked, false);
cd.dec(); assert.equal(invoked, true);

  演習:プライベートデータのためのWeakMap

exercises/weakmaps/weakmaps_private_data_test.mjs

34.4 WeakMap API

`WeakMap`のコンストラクターと4つのメソッドは、`Map`の同等のメソッドと同じように機能します。

  クイズ

クイズアプリをご覧ください。