网站首页 > 博客文章 正文
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
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
猜你喜欢
- 2025-06-13 只需在注册表中新建一个值,就可以禁用键盘上的大写锁定键
- 2025-06-13 MapStruct架构设计(maps模块)
- 2025-06-13 轻松解读源码系列之Java集合接口&抽象类(1)—Map和Collection
- 2025-06-13 Seata源码—4.全局事务拦截与开启事务处理二
- 2025-06-13 rust map与c++区别(rust c++ 性能比较)
- 2025-06-13 「吐血整理」想学Google Guava看这篇就够了
- 2025-06-13 QMap的说明和简单使用(qmap和qhash)
- 2025-06-13 Map的几种遍历方式(map的三种遍历方式js)
- 2025-06-13 Go语言映射(Map)类型详解(go中的map)
- 2025-06-13 golang笔试题(golang面试参考手册)
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- powershellfor (55)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)