面试中经常被问:你了解 WeakMap / WeakSet 吗?
实际开发中也常有人困惑:我什么时候该用 Map,而不是 Object?Weak 到底弱在哪?
这篇文章,我会从最熟悉的 Object 讲起,一步步到 Map、Set,最后深入 WeakMap 和 WeakSet。
一、从 Object 说起:我们最熟悉,也最容易踩坑
在 JavaScript 里,对象几乎无处不在:
const person = { name: "张三" };
console.log(person.name); // 张三
for (const key in person) {
console.log(key, person[key]); // name 张三
}
delete person.name;
console.log(person.name); // undefined
我们对 Object 已经非常熟悉了:
- 可以通过
.或[]访问属性 - 可以用
for...in遍历 - 可以用
delete删除属性
但 Object 天生就不是为了「做集合」设计的。
一个真实的小坑
假设你想做一个“字典”,key 可以是任意值:
const obj = {};
const a = {};
const b = {};
obj[a] = 'A';
obj[b] = 'B';
console.log(obj); // { "[object Object]": "B" }
你以为是两个 key,实际上:
- 对象的 key 只能是字符串或 Symbol
- 非字符串会被隐式转换成字符串
这也是 Map 诞生的原因之一。
二、Map:为“键值对集合”而生
可以把 Map 理解成:一个“升级版 Object”,但专门用来存键值对
1. 创建和添加数据
const map = new Map();
map.set('name', '张三');
map.set('phone', 'iPhone');
特点很明确:
set(key, value)添加数据- key 可以是任意类型(对象、函数、基本类型)
- 同一个 key 只会存在一份
map.set('phone', 'Galaxy'); // 覆盖
2. 读取、判断、长度
map.get('phone'); // Galaxy
map.has('phone'); // true
map.size; // 2
3. Map 是可迭代的
这是它和 Object 的一个重要区别
for (const [key, value] of map) {
console.log(key, value);
}
// name 张三
// phone Galaxy
要仅获取键或值,还有一些方法可供使用
map.keys() // MapIterator {'name', 'phone'}
map.values() // MapIterator {'张三', 'Galaxy'}
map.entries() // MapIterator {'name' => '张三', 'phone' => 'Galaxy'}
map.forEach(item => {})
甚至可以直接展开:
[...map]; // [['name', '张三'],['phone', 'Galaxy']]
4. 删除与清空
map.delete('phone'); // true
// 清空所有
map.clear(); // Map(0) {}
三、WeakMap:真正让人迷惑的地方来了
WeakMap起源于Map,因此它们彼此非常相似。但是,WeakMap 具有很大的不同
弱?弱在哪里?
核心一句话
WeakMap 的 key 是“弱引用”,不会阻止垃圾回收
1. key 只能是对象
const wm = new WeakMap();
wm.set({}, 'data'); // ✅
wm.set('a', 1); // ❌ TypeError
原因很简单:
- WeakMap 的设计目标:绑定对象的“附加信息”
- 如果 key 是基本类型,就谈不上 GC
2. 为什么不能遍历?
想象这样一个场景:
let user = { name: 'John' };
const wm = new WeakMap();
wm.set(user, 'meta');
user = null; // 断开引用
这时候:
- 垃圾回收 随时可能发生
- WeakMap 中的数据 可能突然消失
如果还能遍历,那结果就是不稳定的
所以 ES 规范直接规定:
- ❌ 不可遍历
- ❌ 没有 size
- ✅ 只有
get / set / has / delete
3. WeakMap 的真实使用场景
一个非常经典的例子:
const wm = new WeakMap();
function process(obj) {
if (!wm.has(obj)) {
wm.set(obj, { count: 0 });
}
wm.get(obj).count++;
}
- 不污染原对象
- 对象销毁后,数据自动释放
- 不会内存泄漏
这也是 WeakMap 最大的价值。
四、Set:只关心“值是否存在”
如果说 Map 是 Object 的替代品,
那 Set 更像是“升级版数组” 。
1. 成员唯一
const set = new Set();
set.add(1);
set.add(1);
set.add(NaN);
set.add(NaN);
结果:
Set { 1, NaN }
规则总结:
- 基本类型:值相同 → 只存一个
- 引用类型:地址相同 → 只存一个
NaN在 Set 中被认为是“相等的”
2. 可遍历
for (const val of set) {}
set.forEach(val => {})
3. 实战:数组去重、交并差集
[...new Set([1,1,2,3])]; // [1,2,3]
Set 在这类场景下,简洁又高效。
五、WeakSet:存在,但很低调
WeakSet 和 WeakMap 的理念是一样的:
- 成员是对象
- 成员是弱引用
- 不可遍历
let obj = { a: 1 };
const ws = new WeakSet();
ws.add(obj);
obj = null; // 被 GC
你永远不知道它什么时候“少了一个成员”,
所以:
WeakSet 适合做“对象存在性标记”,而不是数据容器
六、一张表彻底记住它们
| 类型 | key/value 限制 | 是否可遍历 | GC 影响 |
|---|---|---|---|
| Object | key 只能是字符串 | 可遍历 | 强引用 |
| Map | key 任意 | ✅ | 强引用 |
| WeakMap | key 只能是对象 | ❌ | 弱引用 |
| Set | value 任意 | ✅ | 强引用 |
| WeakSet | value 只能是对象 | ❌ | 弱引用 |
如何一句话答 WeakMap / WeakSet区别
WeakMap / WeakSet 的核心在于“弱引用 + 不可遍历”,
它们不会阻止垃圾回收,适合存放与对象生命周期绑定的附加数据,用来避免内存泄漏。
如果这篇文章对你有帮助,欢迎点赞、收藏,