专业的编程技术博客社区

网站首页 > 博客文章 正文

宇宙厂:WeakMap 和 WeakSet 和垃圾回收器有什么关系?

baijin 2025-06-13 11:19:22 博客文章 7 ℃ 0 评论

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

1. 理解 Map 和 Set 与垃圾回收机制

在 JavaScript 中,当对象外部的所有引用都丢失时, 对象中的值 (Values in Object) 不会被垃圾回收,即:只要对象可访问,其属性 (properties) 和元素 (elements) 就都是可访问的。这可能会导致潜在的内存泄漏,典型场景包括 Map 和 Set。

下面是使用普通 Object 的例子:

let o = {name: "高级前端技术进阶"};
const o2 = {author: o, location: "杭州"};
o = null;
// 注意:只是将 o 对堆内存中对象的引用断开而已,o2 引用依然存在
console.log(o2);
// 输出 {"author": {"name": "高级前端技术进阶"},"location": "杭州"}

而 Map 和 Set 也是同样的道理,比如下面的 Map 示例:

let myKey = {a: 1};
const myMap = new Map([[myKey, "some value"]]);
myKey = {};
// 除了 Map 外,所有针对 {a: 1} 的引用丢失
// 但是迭代 myMap 依然能访问值 {a: 1}
for (const [key, value] of myMap) {
  console.log(key);
}
// 打印 {a: 1}

在上面的示例代码中,对象 {a: 1} 以及 Map 中与该键关联的值都不会被垃圾回收,从而产生内存泄漏。如果代码中没有其他对象引用该键,那么该键及其关联的值实际上应该要垃圾回收。

2. 理解 WeakMap 和 WeakSet 中的弱引用

WeakMap 和 WeakSet 对象与其非弱引用 Map 和 Set 非常类似。不同之处在于,前者的键是弱引用,即如果没有其他对象强引用这些键,标记 - 清除算法 (mark-and-sweep algorithm) 不会将键视为可访问的,从而避免内存泄漏。

注意:WeakMap 是键值对的集合,其键必须是对象或未注册的 Symbol(未注册的 Symbol 保证唯一且不能被重新创建),其值可以是任意 JavaScript 类型,并且不会为其键创建强引用。

需要注意的是,WeakMap 和 WeakSet 的键只能弱引用对象。在 JavaScript 中,原始值不会被引用,而是强存储在变量中,即存储在键中。对于原始值的键,弱引用键的移除操作永远不会触发,从而使其永远保留在集合中。

let myKey = {a: 1};
const myMap = new WeakMap([[myKey, "some value"]]);
myKey = {};
// 现在对象 {a: 1} 和所有相关的值都会被垃圾收集
let myKey = "someKey";
const myMap = new WeakMap([[myKey, "some value"]]);
// TypeError: WeakMap 的键必须是对象

3.WeakMap 和 WeakSet 不可迭代

3.1 为什么 WeakMap 和 WeakSet 不可迭代

MDN 的解释如下:

If they were, the list would depend on the state of garbage collection, introducing non-determinism.

首先需要明白一点,即垃圾回收是不确定的。JavaScript 程序是否被垃圾回收器回收,完全取决于其运行环境。假设有以下程序:

const weakMap = new WeakMap();
const key1 = {data: 123};
weakMap.set(key1, "value");
weakMap.set({data: 456}, "value");
for (const [key, value] of weakMap) {
  // 实际上这是不支持的
  console.log(`${key} is ${value}`);
}

那么,开发者期望这个程序会输出什么结果?

无法确定! 因为实际上 {data: 456} 创建的对象只在 weakMap 中被引用,因此该元素会被垃圾回收器回收。但开发者不能确定(甚至不太可能)垃圾回收器在迭代 weakMap 时是否已经将其从 weakMap 中移除,即代码表现出不可预测的行为。

说得更直白点,如果 WeakMap 暴露任何方法来获取其键的列表,该列表将依赖于垃圾回收的状态,从而引入不确定性。因此,如果确实需要获取键的列表,则应该使用 Map 而不是 WeakMap。

3.2 WeakMap 和 WeakSet 不可迭代的示例

前文讲过,针对 WeakMap 和 WeakSet 来说, 需要考虑集合中元素数量和顺序的方法和操作并不可靠,因此没有 size 方法,也无法对元素进行迭代。

WeakMap 只有 set(key, value)、get(key)、delete(key) 和 has(key) 这几个方法。WeakSet 只有 add(value)、delete(value) 和 has(value) 这几个方法。

let myKey = {a: 1};
const myMap = new WeakMap([
  [myKey, "some value"],
  [[2], "..."],
]);

for (const [key, value] of myMap) {
  console.log(key);
}
// 报错 TypeError: myMap 不是可迭代的

需要注意的是,当对象不再具有强引用时,无论是否发生垃圾收集,get(key) 和 has(key) 将不起作用:

let myKey = {a: 1};
const myMap = new Map([
  // 不是 WeakMap,所以不会垃圾回收
  [myKey, "some value"],
  [[2], "..."],
]);
myKey = null;
console.log(myMap.get(myKey));
// 打印 undefined, 键 null 不存在值
console.log(myMap.get({ a: 1}));
// 打印  undefined
// 因为 {a: 1} !== { a: 1 },即对象不相等

4. 使用 WeakMap 和 WeakSet 存储特定数据

在下面的示例中,函数 useObj() 计算在特定对象上调用该函数的次数,并将每个对象的计数存储在 WeakMap 中。

const callCount = new WeakMap();
function useObj(obj) {
  // 对 obj 执行任何操作
  if (!callCount.has(obj)) {
    obj.status = "active";
  }
  // 更新 callCount 的值
  let called = callCount.get(obj) || 0;
  called++;
  // 调用次数 + 1
  callCount.set(obj, called);
}
// 临时对象在整个程序中通过 useObj() 被多次使用
let tempObj = {name: "some temporary object"};
useObj(tempObj);
useObj(tempObj);
useObj(tempObj);
console.log(
  `Object "${tempObj.name}" was used ${callCount.get(tempObj)} times`
);
// 打印: Object "some temporary object" was used 3 times
// 此时对象也不再需要
tempObj = null;
// 对象 {name: "some temporary object"} 和关联的对象可以垃圾回收

WeakSet 为开发者提供了 has() 方法来读取集合,从而使得 WeakSet 适合用来追踪临时对象的存在。

const mySet = new WeakSet();
let tempObj1 = {},
  tempObj2 = {},
  tempObj3 = ["..."],
  tempObj4 = {};
mySet.add(tempObj1);
mySet.add(tempObj2);
mySet.add(tempObj3);
mySet.add(tempObj4);
console.log(mySet.has(tempObj3));
// 打印: true
tempObj3 = null;
console.log(mySet.has(tempObj3));
// 打印: false
// 数组 ["..."] 此时会排队进入垃圾回收流程

参考资料

https://stackoverflow.com/questions/51009210/why-are-weakmaps-not-interable

https://library.fridoverweij.com/docs/jstutorial/weakmaps_and_weaksets.html

https://javascript.plainenglish.io/javascript-set-vs-weakset-differences-best-practices-8d32423c6486

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表