WeakMap

Baseline 廣泛可用 *

此特性已相當成熟,可在許多裝置和瀏覽器版本上使用。自 ⁨2015 年 7 月⁩以來,各瀏覽器均已提供此特性。

* 此特性的某些部分可能存在不同級別的支援。

WeakMap 是一種鍵值對集合,其鍵必須是物件或非註冊 Symbol,值為任意 JavaScript 型別,並且它不會對其鍵建立強引用。也就是說,一個物件作為 WeakMap 的鍵,並不會阻止該物件被垃圾回收。一旦用作鍵的物件被回收,它在任何 WeakMap 中的對應值也就成為垃圾回收的候選物件,前提是它們沒有被其他地方強引用。唯一可以用作 WeakMap 鍵的原始型別是 Symbol——更具體地說,是非註冊 Symbol——因為非註冊 Symbol 被保證是唯一的,並且不能被重新建立。

WeakMap 允許以一種不阻止鍵物件被回收的方式將資料與物件關聯起來,即使值引用了鍵。但是,WeakMap 不允許觀察其鍵的存活性,這也是它不允許列舉的原因;如果 WeakMap 暴露任何獲取其鍵列表的方法,該列表將取決於垃圾回收的狀態,從而引入了不確定性。如果你需要鍵的列表,你應該使用 Map 而不是 WeakMap

你可以在 鍵值集合 指南的 WeakMap 物件 部分了解更多關於 WeakMap 的資訊。

描述

WeakMap 的鍵必須是可被垃圾回收的。大多數 原始資料型別都可以任意建立,並且沒有生命週期,因此不能用作鍵。物件和 非註冊 Symbol 可以用作鍵,因為它們是可被垃圾回收的。

鍵的相等性

與常規 Map 一樣,值相等基於 SameValueZero 演算法,這與 === 運算子相同,因為 WeakMap 只能包含物件和 Symbol 鍵。這意味著對於物件鍵,相等基於物件標識。它們是透過 引用而不是值進行比較的。

為什麼使用 WeakMap?

可以使用兩個陣列(一個用於鍵,一個用於值)來在 JavaScript 中實現一個 Map API,這四個 API 方法共享這兩個陣列。向這個 Map 設定元素需要同時將鍵和值推到這兩個陣列的末尾。結果是,鍵和值的索引將對應於兩個陣列。從 Map 中獲取值需要遍歷所有鍵以查詢匹配項,然後使用該匹配項的索引從值陣列中檢索相應的值。

這樣的實現會有兩個主要的不便之處:

  1. 第一個是不便之處在於設定和搜尋操作需要 O(n) 的時間複雜度(n 是 Map 中的鍵的數量),因為這兩個操作都必須遍歷鍵列表以查詢匹配的值。
  2. 第二個不便之處是記憶體洩漏,因為陣列會永久維護對每個鍵和每個值的引用。這些引用會阻止鍵被垃圾回收,即使沒有其他對該物件的引用。這也會阻止相應的值被垃圾回收。

相比之下,在 WeakMap 中,一個鍵物件只要該鍵沒有被垃圾回收,就會對其內容強引用,但之後則弱引用。因此,WeakMap

  • 不會阻止垃圾回收,從而最終移除對鍵物件的引用
  • 如果鍵物件除了 WeakMap 之外沒有其他引用,則允許垃圾回收任何值

當將鍵對映到僅在鍵未被垃圾回收時才具有價值的資訊時,WeakMap 可以是一個特別有用的結構。

但是,由於 WeakMap 不允許觀察其鍵的存活性,因此其鍵是不可列舉的。沒有方法可以獲取鍵的列表。如果存在,列表將取決於垃圾回收的狀態,從而引入了不確定性。如果你需要鍵的列表,你應該使用 Map

建構函式

WeakMap()

建立一個新的 WeakMap 物件。

例項屬性

這些屬性定義在 WeakMap.prototype 上,並被所有 WeakMap 例項共享。

WeakMap.prototype.constructor

建立例項物件的建構函式。對於 WeakMap 例項,初始值為 WeakMap 建構函式。

WeakMap.prototype[Symbol.toStringTag]

[Symbol.toStringTag] 屬性的初始值為字串 "WeakMap"。此屬性用於 Object.prototype.toString()

例項方法

WeakMap.prototype.delete()

從此 WeakMap 中移除由鍵指定的條目。

WeakMap.prototype.get()

返回此 WeakMap 中與鍵對應的value,如果不存在則返回 undefined

WeakMap.prototype.getOrInsert() 實驗性

返回此 WeakMap 中與指定鍵對應的 value。如果鍵不存在,則使用給定的預設值插入新條目,並返回插入的值。

WeakMap.prototype.getOrInsertComputed() 實驗性

返回此 WeakMap 中與指定鍵對應的 value。如果鍵不存在,則使用給定回撥函式計算出的預設值插入新條目,並返回插入的值。

WeakMap.prototype.has()

返回一個布林值,指示此 WeakMap 中是否存在具有指定鍵的條目。

WeakMap.prototype.set()

向此 WeakMap 新增具有指定鍵和值的條目,如果鍵已存在,則更新現有條目。

示例

使用 WeakMap

js
const wm1 = new WeakMap();
const wm2 = new WeakMap();
const wm3 = new WeakMap();
const o1 = {};
const o2 = () => {};
const o3 = window;

wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // a value can be anything, including an object or a function
wm2.set(o2, undefined);
wm2.set(wm1, wm2); // keys and values can be any objects. Even WeakMaps!

wm1.get(o2); // "azerty"
wm2.get(o2); // undefined, because that is the set value
wm2.get(o3); // undefined, because there is no key for o3 on wm2

wm1.has(o2); // true
wm2.has(o2); // true (even if the value itself is 'undefined')
wm2.has(o3); // false

wm3.set(o1, 37);
wm3.get(o1); // 37

wm1.has(o1); // true
wm1.delete(o1);
wm1.has(o1); // false

使用 .clear() 方法實現一個類似 WeakMap 的類

js
class ClearableWeakMap {
  #wm;
  constructor(init) {
    this.#wm = new WeakMap(init);
  }
  clear() {
    this.#wm = new WeakMap();
  }
  delete(k) {
    return this.#wm.delete(k);
  }
  get(k) {
    return this.#wm.get(k);
  }
  has(k) {
    return this.#wm.has(k);
  }
  set(k, v) {
    this.#wm.set(k, v);
    return this;
  }
}

模擬私有成員

開發人員可以使用 WeakMap 將私有資料與物件關聯起來,具有以下優點:

  • Map 相比,WeakMap 不持有對用作鍵的物件本身的強引用,因此元資料與物件本身具有相同的生命週期,避免了記憶體洩漏。
  • 與使用非列舉和/或 Symbol 屬性相比,WeakMap 存在於物件外部,並且使用者程式碼無法透過反射方法(如 Object.getOwnPropertySymbols)檢索元資料。
  • 閉包相比,同一個 WeakMap 可以被同一建構函式建立的所有例項重用,使其更節省記憶體,並允許同一類的不同例項讀取彼此的私有成員。
js
let Thing;

{
  const privateScope = new WeakMap();
  let counter = 0;

  Thing = function () {
    this.someProperty = "foo";

    privateScope.set(this, {
      hidden: ++counter,
    });
  };

  Thing.prototype.showPublic = function () {
    return this.someProperty;
  };

  Thing.prototype.showPrivate = function () {
    return privateScope.get(this).hidden;
  };
}

console.log(typeof privateScope);
// "undefined"

const thing = new Thing();

console.log(thing);
// Thing {someProperty: "foo"}

thing.showPublic();
// "foo"

thing.showPrivate();
// 1

這大致相當於使用私有欄位的以下程式碼:

js
class Thing {
  static #counter = 0;
  #hidden;
  constructor() {
    this.someProperty = "foo";
    this.#hidden = ++Thing.#counter;
  }
  showPublic() {
    return this.someProperty;
  }
  showPrivate() {
    return this.#hidden;
  }
}

console.log(thing);
// Thing {someProperty: "foo"}

thing.showPublic();
// "foo"

thing.showPrivate();
// 1

關聯元資料

WeakMap 可用於將元資料與物件關聯起來,而不會影響物件本身的生命週期。這與私有成員的示例非常相似,因為私有成員也被建模為外部元資料,不參與 原型鏈繼承。

此用例可以擴充套件到已建立的物件。例如,在 Web 上,我們可能希望將額外資料與 DOM 元素關聯起來,DOM 元素稍後可以訪問這些資料。一種常見的方法是將資料附加為屬性:

js
const buttons = document.querySelectorAll(".button");
buttons.forEach((button) => {
  button.clicked = false;
  button.addEventListener("click", () => {
    button.clicked = true;
    const currentButtons = [...document.querySelectorAll(".button")];
    if (currentButtons.every((button) => button.clicked)) {
      console.log("All buttons have been clicked!");
    }
  });
});

這種方法有效,但也有一些陷阱:

使用 WeakMap 可以解決這些問題:

js
const buttons = document.querySelectorAll(".button");
const clicked = new WeakMap();
buttons.forEach((button) => {
  clicked.set(button, false);
  button.addEventListener("click", () => {
    clicked.set(button, true);
    const currentButtons = [...document.querySelectorAll(".button")];
    if (currentButtons.every((button) => clicked.get(button))) {
      console.log("All buttons have been clicked!");
    }
  });
});

在這裡,只有擁有對 clicked 訪問許可權的程式碼才能知道每個按鈕的點選狀態,並且外部程式碼無法修改狀態。此外,如果任何按鈕從 DOM 中移除,關聯的元資料將自動被垃圾回收。

快取

你可以將傳遞給函式的物件與函式的返回值關聯起來,這樣如果再次傳遞同一個物件,就可以返回快取的結果而無需重新執行函式。如果函式是純函式(即它不修改任何外部物件或引起其他可觀察的副作用),這一點很有用。

js
const cache = new WeakMap();
function handleObjectValues(obj) {
  if (cache.has(obj)) {
    return cache.get(obj);
  }
  const result = Object.values(obj).map(heavyComputation);
  cache.set(obj, result);
  return result;
}

這隻在函式的輸入是物件時才有效。此外,即使輸入不再被傳遞,只要鍵(輸入)仍然存活,結果就會永遠保留在快取中。更有效的方法是使用一個 MapWeakRef 物件配對,這允許你將任何型別的輸入值與其各自(可能很大)的計算結果關聯起來。有關更多詳細資訊,請參閱 WeakRefs 和 FinalizationRegistry 示例。

規範

規範
ECMAScript® 2026 語言規範
# sec-weakmap-objects

瀏覽器相容性

另見