为了彻底理清 JavaScript 的集合体系,我们需要从最底层的内存管理机制出发。无论是 Map 还是 Set,它们最核心的区别不在于 API 的好用程度,而在于它们如何与系统的**垃圾回收(Garbage Collection, GC)**对话。
第一部分:核心底层——强引用 vs 弱引用
在 JavaScript 中,内存管理是自动的,但它是基于“可达性”逻辑的。
1. 强引用 (Strong Reference)
这是 JS 的默认行为。只要一个变量指向一个对象,这个对象就是“可达”的,垃圾回收器绝不会碰它。
-
逻辑:
A -> Object。只要 A 还在,Object 就在。 -
代价:如果你忘记手动将
A = null,Object 会永远占用内存,这就是内存泄漏的根源。
2. 弱引用 (Weak Reference)
弱引用不会被垃圾回收器计入。它像是一个“旁观者”。
-
逻辑:
A --(weak)--> Object。如果没有其他强引用指向这个 Object,即便 A 还在看着它,垃圾回收器也会在下次清理时直接把 Object 收走。 -
代价:你无法遍历弱引用的集合,因为对象可能随时消失,导致结果不可预测。
第二部分:Map 与 WeakMap 的深度梳理
你之前的疑惑集中在:既然逻辑一样,为什么要用 WeakMap?
1. 场景回顾:私有属性与 this
在 class 或 function 中,我们利用 WeakMap 实现私有化。
- 代码示例(Function 写法):
JavaScript
const privates = new WeakMap();
function User(name) {
// 这里的 this 指向 new 出来的实例对象
// 我们把实例对象作为 Key,把私有数据作为 Value
privates.set(this, { name: name });
}
User.prototype.getName = function() {
return privates.get(this).name;
};
-
为什么要用 WeakMap 而不是 Map?
如果用
Map,当你执行user = null试图销毁用户时,Map依然强引用着user实例作为 Key。结果是:user实例和它的私有数据{ name }都会残留在内存中。使用
WeakMap,当user = null后,唯一的强引用断开,WeakMap里的这条记录会被 GC 自动抹除。
2. 场景回顾:DOM 节点元数据
-
逻辑:给 DOM 元素“贴标签”。
-
优势:当 DOM 元素从页面移除(例如 Vue/React 组件卸载)时,
WeakMap里的标签会自动消失,无需手动清理Map.delete(el)。
第三部分:Set 与 WeakSet 的系统梳理
Set 系列处理的是值的集合(类似数组,但值唯一),而不是键值对。
1. Set:唯一值的容器
Set 是强引用的,可以存储任何类型的值。
- 用法:去重、判断是否存在。
JavaScript
const tags = new Set(['js', 'css', 'js']); // 结果只有 'js' 和 'css'
tags.add('html');
console.log(tags.has('js')); // true
2. WeakSet:对象的“临时通行证”
WeakSet 只能存储对象,且对这些对象是弱引用。
-
底层逻辑:它不存储值,它只存储“对象的引用”。如果对象在外部被销毁,它在
WeakSet里也会消失。 -
特性:不可遍历,没有
size。
3. WeakSet 的典型用处:防重入与打标签
假设你有一个复杂的函数,你希望确保某些对象只被处理一次,且不干扰它们的回收。
JavaScript
const processedObjects = new WeakSet();
function complexProcess(obj) {
if (processedObjects.has(obj)) {
return; // 如果已经处理过,直接跳过
}
// 执行复杂的业务逻辑...
console.log("正在处理对象...", obj);
// 处理完打个标签
processedObjects.add(obj);
}
let data = { id: 1 };
complexProcess(data); // 处理
complexProcess(data); // 自动跳过
data = null; // 当 data 设为 null,WeakSet 自动释放对原对象的引用
第四部分:四种集合结构全对比总结
| 结构 | 存储内容 | 唯一性依据 | 引用强度 | 核心价值 |
|---|---|---|---|---|
| Map | 任意键值对 | 键唯一 | 强引用 | 灵活的字典,支持各种类型的 Key |
| WeakMap | 对象为键的键值对 | 键(对象)唯一 | 弱引用(键) | 内存安全地关联元数据、私有属性 |
| Set | 任意值 | 值唯一 | 强引用 | 高效去重、集合运算(交/并/差) |
| WeakSet | 仅限对象 | 对象唯一 | 弱引用 | 内存安全地标记对象状态(如:是否被处理过) |
关键结论:如何做选择?
-
如果你需要处理字符串、数字等基础类型:只能选
Map或Set。 -
如果你需要遍历、统计数量:只能选
Map或Set。 -
如果你在处理 DOM 节点或类实例,且不希望干扰它们的生死(GC):请务必选择
WeakMap或WeakSet。