标准答案
- Map 是键值对集合,键可以是任意类型,并且保留插入顺序。它适合做真正的映射表,例如用对象、DOM 节点或请求参数对象作为 key。
- Set 是唯一值集合,适合去重、成员判断和集合运算。Set 内部判断相等使用 SameValueZero,所以 NaN 会被认为是同一个值。
- WeakMap 的键必须是对象,WeakSet 的值也必须是对象。它们保存的是弱引用,不会因为集合本身存在就阻止对象被垃圾回收。
- WeakMap 和 WeakSet 不可遍历,也没有 size。这个限制和 GC 不确定性有关:如果允许遍历,就会让程序观察到对象什么时候被回收。WeakMap 适合存对象私有数据、DOM 元数据、对象级缓存。
题目解析
Object 也能存键值,但普通对象的键主要是字符串和 Symbol,使用对象作为键时会被转成字符串,容易出现冲突。Map 的 key 不做这种字符串化,更适合表达“任意值到任意值”的映射关系。
Map/Set 是强引用集合。只要对象还在 Map 或 Set 里,即使业务上已经不用它,也不会被 GC 回收。因此长期缓存要考虑清理策略,不能无限增长。
WeakMap 的价值不是“更快”,而是不会让 key 因缓存存在而被保活。比如给 DOM 节点挂元数据时,节点被移除且没有其它引用后,对应 WeakMap 数据也可以随之回收。
WeakMap 不能替代普通 Map。需要遍历、统计数量、展示列表、按插入顺序输出时,仍然应该使用 Map 或数组。
代码示例
下面例子展示对象键在 Object 和 Map 中的差异,以及 WeakMap 保存对象元数据的典型用法。
JavaScript
const a = { id: 1 }
const b = { id: 2 }
const obj = {}
obj[a] = 'A'
obj[b] = 'B'
console.log(obj) // { "[object Object]": "B" }
const map = new Map()
map.set(a, 'A')
map.set(b, 'B')
console.log(map.get(a)) // A
const meta = new WeakMap()
meta.set(a, { selected: true })
console.log(meta.get(a).selected) // true常见误区
- 误以为 WeakMap 是性能更好的 Map。
- 试图遍历 WeakMap 或读取 size。
- 用普通对象存任意对象键,导致键被转成字符串。