Map、Set、WeakMap、WeakSet 都是 ES6 引入的集合类型,它们的关系如下:
- Set 与 Map :标准的键值对或值的集合,对键/值是强引用,提供遍历、大小、清空等完整 API。
- WeakMap 与 WeakSet :只能以对象 作为键(WeakSet 的值也必须是对象),且持有的是弱引用 ,不可遍历,没有
size属性。
一、Map vs WeakMap
| 特性 | Map | WeakMap |
|---|---|---|
| 键的类型 | 任意类型(对象、基本类型等) | 必须是对象(不能是 null) |
| 值类型 | 任意 | 任意 |
| 引用类型 | 对键和值都是强引用 | 对键是弱引用,值平时是强引用(但当键被回收后,值也会被回收) |
| 是否可迭代 | 可迭代(for...of、forEach) |
不可迭代 |
| 获取大小 | map.size |
没有 size 属性 |
| 方法 | set, get, has, delete, clear, keys(), values(), entries() |
set, get, has, delete (无 clear、无遍历方法) |
| 何时使用 | 需要存储键值对,并且可能需要遍历或查看大小 | 需要为对象关联额外数据,且这些数据在对象消失后应自动清理 |
弱引用意味着什么?
js
let obj = { name: 'Alice' };
const wm = new WeakMap();
wm.set(obj, 'private data');
console.log(wm.get(obj)); // 'private data'
obj = null; // 移除对原对象的强引用
// 此时,原对象没有其他强引用,垃圾回收器会回收它,WeakMap 中的条目也会自动消失(即使关联了私有数据)
为什么 WeakMap 不可遍历?
因为垃圾回收的执行时机不确定,WeakMap 的内容随时可能变化。如果提供迭代功能(forEach、size 等),你会得到一个不稳定的快照,容易产生错误。
经典用途:
- 为 DOM 元素关联私有数据,当元素被移除后自动释放。
- 实现类的私有属性(通过实例作为键)。
二、Set vs WeakSet
| 特性 | Set | WeakSet |
|---|---|---|
| 值的类型 | 任意类型 | 必须是对象 |
| 引用类型 | 对值是强引用 | 对值是弱引用 |
| 是否可迭代 | 可迭代 | 不可迭代 |
| 获取大小 | set.size |
没有 size |
| 方法 | add, has, delete, clear, keys(), values(), entries() |
add, has, delete (无 clear、无遍历方法) |
| 何时使用 | 需要存储不重复的值,或进行集合运算 | 存储对象是否存在,不关心遍历,且对象消失后应自动移除 |
实例
js
let user1 = { name: 'Alice' };
let user2 = { name: 'Bob' };
const ws = new WeakSet([user1, user2]);
console.log(ws.has(user1)); // true
user1 = null; // 移除强引用后,user1 对应的对象被回收,WeakSet 中的记录也消失
三、核心区别总结
| Map | Set | WeakMap | WeakSet | |
|---|---|---|---|---|
| 存储结构 | 键值对 | 唯一值 | 键值对(键必为对象) | 唯一值(值必为对象) |
| 键/值要求 | 键任意 | 值任意 | 键必须对象 | 值必须对象 |
| 引用强度 | 强引用 | 强引用 | 弱引用(键) | 弱引用(值) |
| 可迭代性 | 可迭代 | 可迭代 | 不可迭代 | 不可迭代 |
有 size |
✅ | ✅ | ❌ | ❌ |
有 clear() |
✅ | ✅ | ❌ | ❌ |
| 典型场景 | 缓存、字典 | 去重、集合操作 | DOM 关联数据、私有属性 | 对象标记、防止重复 |
四、何时用 WeakMap/WeakSet?
- 内存敏感且不需要统计全部信息时,使用弱引用结构可以避免手动清理,减少内存泄漏风险。
- 如果你需要遍历、检查大小或清空整个集合,那就必须用 Map/Set。
比如在 Web 应用中,一个页面可能有成千上万个动态 DOM 元素,为每个元素绑定的数据使用 WeakMap,当元素被移除后,占用的内存自动释放,比手动清除方便且安全。