人人都会AI编程

WeakMap、WeakSet 的弱引用特性与适用场景

更新时间:2026-07-11

在 ES6 引入的 Map 和 Set 之外,JavaScript 还提供了它们的“弱引用”版本:WeakMap 和 WeakSet。它们与 Map/Set 的核心区别在于对(WeakMap)或(WeakSet)持有的引用是弱引用,正是这一特性决定了它们独特的使用场景。

什么是弱引用

在 JavaScript 的垃圾回收机制中,一个对象只要存在至少一个强引用指向它,就不会被回收。而弱引用则完全不同:如果一个对象仅被弱引用指向,没有任何强引用,那么垃圾回收器可以随时回收它,即便它仍然存在于 WeakMap 或 WeakSet 中。

换句话说,WeakMap 和 WeakSet 不会阻止垃圾回收器回收它们所引用的对象。你往 WeakMap 里放了一个对象作为键,但在别处都不再使用这个对象时,它就自动“消失”了,对应的键值对也会被清除。

WeakMap 的特性与限制

WeakMap 的键必须是对象(不能是字符串、数字等原始类型),值可以是任意类型。这种设计从根本上是匹配弱引用语义的:如果允许原始类型作为键,弱引用就无法奏效,因为原始类型按值传递、没有引用计数的概念。

由于弱引用无法被迭代,WeakMap 不可遍历:它没有 size 属性,也不支持 keys()values()entries() 等迭代方法,更不能用 forEach。你只能通过 get(key)set(key, value)has(key)delete(key) 操作已知的键。这种“不透明”的特性正是为了保证垃圾回收的确定性:如果允许遍历,垃圾回收器就无法判断哪些键还“有用”。

let obj = { name: 'temp' };
const wm = new WeakMap();
wm.set(obj, 'some metadata');

console.log(wm.get(obj)); // 'some metadata'

// 当 obj 失去所有强引用后
obj = null;
// 键值对会在下一次垃圾回收时自动清除,无法再被访问

WeakSet 的特性与限制

WeakSet 与 WeakMap 类似,它只能存放对象作为成员,不允许原始类型。同样地,WeakSet 不可遍历,没有 size 属性和遍历方法。你只能通过 add()delete()has() 操作。当一个对象在程序其他地方不再被持有,WeakSet 中对应的成员会被自动移除。

let user = { id: 1 };
const activeUsers = new WeakSet();
activeUsers.add(user);

console.log(activeUsers.has(user)); // true

user = null;
// 对象清除了,对应 WeakSet 成员也会消失

典型适用场景

WeakMap 和 WeakSet 是为元数据存储对象标签而生的,当你需要将额外信息“贴”在某个对象上,但又不想因为这个“贴标签”行为阻止该对象被正常回收,它们就是最佳选择。

1. 存储私有数据或关联的元数据

在类和对象系统中,有时需要将一些内部状态与某个实例关联,但不方便作为实例本身的属性。WeakMap 可以轻松实现这一点:

const privateData = new WeakMap();

class User {
  constructor(name, secret) {
    privateData.set(this, { secret });
    this.name = name;
  }
  getSecret() {
    return privateData.get(this).secret;
  }
}

const u = new User('Alice', 'my-password');
console.log(u.getSecret()); // 'my-password'
// 当 u 不再被使用时,WeakMap 中的关联数据也会被自动回收

这种方式相比将数据直接作为属性放在实例上,既保证了数据不被外部轻易访问,又不会影响垃圾回收。

2. 缓存计算结果(避免内存泄漏)

当你想对一个对象缓存某些计算结果,但不想因为这个缓存而阻止对象在不需要时被回收,WeakMap 就是理想载体。相比普通 Map 如果忘记手动清理会导致内存泄漏,WeakMap 会自动清理垃圾对象对应的缓存项。

const cache = new WeakMap();

function heavyCalculation(obj) {
  if (cache.has(obj)) {
    return cache.get(obj);
  }
  const result = /* 昂贵的计算 */;
  cache.set(obj, result);
  return result;
}

3. 标记对象的状态(WeakSet)

WeakSet 适合用来给对象打标签,比如记录哪些对象正在被处理、已访问过等。因为 WeakSet 不会阻止对象被回收,所以用它来跟踪动态生命周期中的对象状态非常安全。

const visited = new WeakSet();

function process(node) {
  if (visited.has(node)) return;
  visited.add(node);
  // ... 进行某种处理
}

在 DOM 操作中,也可以使用 WeakSet 来标记那些已经绑定了事件的 DOM 节点,避免重复绑定,同时不会因标记而阻止 DOM 节点被移除。

4. 防止内存泄漏的观察者模式

在实现发布-订阅模式或者事件监听时,如果订阅者是一个对象,直接将回调与对象关联存储在普通 Map 中,将导致对象无法释放。而使用 WeakMap 可以让订阅者在不再被其他地方引用时自动清理,避免长期订阅导致的内存泄漏。

选型判断:Map 还是 WeakMap?

  • 如果你的键需要遍历(比如列出所有键),或者键本身是字符串/数字,只能用 Map。
  • 如果你希望对象作为键,并且不想因为存储了这些键而阻止它们被垃圾回收,就应该用 WeakMap。
  • 如果你需要知道集合的大小(size),也需要使用 Map 或 Set。

简单记忆原则:只要存储额外信息不想影响原有对象的生命周期,就用 Weak 版本。 这一机制在框架底层、图形编辑器、缓存系统和事件监听管理中被广泛使用,是编写内存友好型 JavaScript 应用的基础工具。