Movatterモバイル変換


[0]ホーム

URL:


  1. 面向开发者的 Web 技术
  2. JavaScript
  3. JavaScript 参考
  4. JavaScript 标准内置对象
  5. WeakMap

此页面由社区从英文翻译而来。了解更多并加入 MDN Web Docs 社区。

View in EnglishAlways switch to English

WeakMap

Baseline Widely available *

This feature is well established and works across many devices and browser versions. It’s been available across browsers since ⁨2015年7月⁩.

* Some parts of this feature may have varying levels of support.

WeakMap 是一种键值对的集合,其中的键必须是对象或非全局注册的符号,且值可以是任意的JavaScript 类型,并且不会创建对它的键的强引用。换句话说,一个对象作为WeakMap 的键存在,不会阻止该对象被垃圾回收。一旦一个对象作为键被回收,那么在WeakMap 中相应的值便成为了进行垃圾回收的候选对象,只要它们没有其他的引用存在。唯一可以作为WeakMap 的键的原始类型是非全局注册的符号,因为非全局注册的符号是保证唯一的,并且不能被重新创建。

WeakMap 允许将数据与对象相关联,而不阻止键对象被垃圾回收,即使值引用了键。然而,WeakMap 并不允许观察其键的生命周期,这就是为什么它不允许枚举;如果WeakMap 提供了任何获得其键的列表的方法,那么这些列表将会依赖于垃圾回收的状态,这引入了不确定性。如果你想要可以获取键的列表,你应该使用Map 而不是WeakMap

你可以在带键的集合指南的WeakMap 对象部分了解更多关于WeakMap 的内容。

描述

WeakMap 的键必须是可被垃圾回收的。大多数原始数据类型可以任意地被创建,且没有生命周期,因此不能作为键。对象和非全局注册的符号都可以作为键,因为它们是可被垃圾回收的。

WeakMap 的意义

在 JavaScript 里,map API可以通过使其四个 API 方法共用两个数组(一个存放键,一个存放值)来实现。给这种映射设置值时会同时将键和值添加到这两个数组的末尾。从而使得键和值的索引在两个数组中相对应。当从该映射取值的时候,需要遍历所有的键,然后使用索引从存储值的数组中检索出相应的值。

但这样的实现会有两个很大的缺点:

  1. 首先赋值和搜索操作都是O(n) 的时间复杂度(n 是键值对的个数),因为这两个操作都需要遍历全部整个数组来进行匹配。
  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()

删除任何与key 关联的值。删除之后,WeakMap.prototype.has(key) 将会返回false

WeakMap.prototype.get()

返回与key 关联的值,如果不存在则返回undefined

WeakMap.prototype.has()

返回一个布尔值,断言某个值是否已经与WeakMap 对象中的key 关联。

WeakMap.prototype.set()

WeakMap 对象中的key 设置value。返回该WeakMap 对象。

示例

使用 WeakMap

js
const wm1 = new WeakMap();const wm2 = new WeakMap();const wm3 = new WeakMap();const o1 = {};const o2 = function () {};const o3 = window;wm1.set(o1, 37);wm1.set(o2, "azerty");wm2.set(o1, o2); // value 可以是任意值,包括一个对象或一个函数wm2.set(o2, undefined);wm2.set(wm1, wm2); // 键和值可以是任意对象,甚至另外一个 WeakMap 对象wm1.get(o2); // "azerty"wm2.get(o2); // undefined,设置的值就是 undefinedwm2.get(o3); // undefined,wm2 中没有 o3 这个键wm1.has(o2); // truewm2.has(o2); // true(即使值是 undefined)wm2.has(o3); // falsewm3.set(o1, 37);wm3.get(o1); // 37wm1.has(o1); // truewm1.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 可用于将元数据与对象关联,而不影响对象的生命周期。这与私有成员示例非常相似,因为私有成员也是以外部的形式模拟的元数据,不参与原型继承

这个用例可以扩展到已经创建的对象上。例如,在网页上,我们可能希望将额外的数据与 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("所有按钮被都被点击了!");    }  });});

这种方法是有效的,但是有一些缺点:

使用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("所有按钮被都被点击了!");    }  });});

这段代码里,只有能访问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 对象配对使用,这允许你将任何类型的输入值与各自的(很可能的)计算结果关联。有关更多详细信息,请参阅WeakRef 和 FinalizationRegistry 示例。

规范

Specification
ECMAScript® 2026 Language Specification
# sec-weakmap-objects

浏览器兼容性

参见

Help improve MDN

Learn how to contribute

This page was last modified on byMDN contributors.


[8]ページ先頭

©2009-2025 Movatter.jp