給急躁的程式設計師的 JavaScript(ES2022 版)
請支持這本書:購買捐款
(廣告,請不要封鎖。)

34 弱映射 (WeakMap)(進階)



弱映射類似於映射,但有以下差異

接下來的兩個章節將更詳細地探討這意味著什麼。

34.1 弱映射是黑盒子

無法檢查弱映射內部有什麼

這些限制啟用了安全性屬性。引用Mark Miller

弱映射/鍵對應值的對應只能由同時擁有弱映射和鍵的人觀察或影響。使用 `clear()`,只擁有弱映射的人就可以影響弱映射和鍵到值的對應。

34.2 弱映射的鍵是弱持有的

弱映射的鍵被稱為弱持有的:通常,如果一個物件參照另一個物件,則後者物件在第一個物件存在時無法被垃圾回收。使用弱映射時,情況不同:如果一個物件是鍵且沒有在其他地方被參照,則它可以在弱映射仍然存在時被垃圾回收。這也會導致對應的項目被移除(但沒有辦法觀察到)。

34.2.1 所有弱映射鍵都必須是物件

所有 WeakMap 鍵都必須是物件。如果您使用基本值,將會收到錯誤訊息

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

如果使用基本值作為鍵,WeakMap 將不再是黑盒子。但由於基本值永遠不會被垃圾回收,因此您無法從弱鍵中獲利,而且也可以使用一般 Map。

34.2.2 使用案例:附加值至物件

這是 WeakMap 的主要使用案例:您可以使用它們將值附加至物件,例如

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

在第 A 行,我們將值附加至 obj。在第 B 行,obj 已經可以被垃圾回收,即使 wm 仍然存在。將值附加至物件的這個技巧,等同於將物件的屬性儲存在外部。如果 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 使用這個函式,您會看到只有在第一次呼叫時才會計算結果,而第二次呼叫則會使用快取值

> 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 的建構函式和四個方法與 其等效的 Map 相同

  測驗

請參閱 測驗應用程式