WeakMap
WeakMap 是只能以对象为键、并且不阻止键被垃圾回收的键值对集合。
#type / concept
#status / growing
#resource / javascript
#resource / ecmascript
[!info] related notes
- 所属 MOC: ES6 新特性 MOC, ECMAScript MOC
- 前置概念: ECMAScript集合引用类型
- 并列概念: Map
- 关系笔记: Map vs WeakMap
WeakMap
一句话定义
WeakMap 是一种弱引用键值对集合,键只能是对象;当键对象没有其他引用时,对应的键值对会被垃圾回收,适合给对象附加不干扰生命周期的元数据。
核心机制
基本用法
const wm = new WeakMap()
const obj = { name: 'foo' }
wm.set(obj, { visits: 0 })
wm.get(obj) // { visits: 0 }
wm.has(obj) // true
wm.delete(obj) // true
关键特性
- 键只能是对象(不能是原始值)
- 键是弱引用:不影响垃圾回收
- 不能遍历:没有
keys()、values()、entries()、forEach()、size - 没有
clear()方法
弱引用的含义
let user = { name: 'Alice' }
const metadata = new WeakMap()
metadata.set(user, { role: 'admin' })
console.log(metadata.get(user)) // { role: 'admin' }
user = null // user 对象失去唯一引用
// 之后某个时刻,GC 回收 { name: 'Alice' } 对象
// WeakMap 中对应的键值对也会被自动清除
弱引用意味着:如果键对象在其他地方不再被引用,GC 可以回收它,WeakMap 不会阻止这个过程。你无法手动触发 GC,所以 WeakMap 的条目何时消失是不确定的。
实际用途
1. 给对象附加私有数据
const _private = new WeakMap()
class User {
constructor(name, password) {
_private.set(this, { name, password })
}
getName() {
return _private.get(this).name
}
checkPassword(pwd) {
return _private.get(this).password === pwd
}
}
const user = new User('Alice', 'secret')
user.getName() // "Alice"
// 外部无法直接访问 password
这种方式比闭包更灵活,每个实例的数据独立存储,实例被回收后数据也自动清理。
2. 缓存计算结果
const cache = new WeakMap()
function expensiveProcess(obj) {
if (cache.has(obj)) {
return cache.get(obj)
}
const result = /* 复杂计算 */ obj.data.length * 2
cache.set(obj, result)
return result
}
对象不再使用时,缓存自动清理,不会造成内存泄漏。适合对 DOM 节点、大型数据对象做缓存。
3. 存储 DOM 节点关联数据
const nodeData = new WeakMap()
function bindTooltip(node, text) {
nodeData.set(node, { text, visible: false })
node.addEventListener('mouseenter', () => {
const data = nodeData.get(node)
data.visible = true
showTooltip(node, data.text)
})
node.addEventListener('mouseleave', () => {
nodeData.get(node).visible = false
hideTooltip(node)
})
}
// 当 DOM 节点被移除时,关联数据自动清理
4. 标记已处理对象(配合 WeakSet)
const visited = new WeakMap()
function deepClone(obj, seen = new WeakMap()) {
if (seen.has(obj)) return seen.get(obj)
const copy = Array.isArray(obj) ? [] : {}
seen.set(obj, copy)
for (const key of Object.keys(obj)) {
copy[key] = typeof obj[key] === 'object' ? deepClone(obj[key], seen) : obj[key]
}
return copy
}
与 Map 的关键区别
| 特性 | Map | WeakMap |
|---|---|---|
| 键类型 | 任意 | 仅对象 |
| 弱引用 | 否 | 是 |
| 可遍历 | 是 | 否 |
| 有 size | 是 | 否 |
| GC 行为 | 阻止回收 | 不阻止回收 |
边界与常见误解
- WeakMap 不是更高级的 Map,而是专用工具。它适合”给对象附加数据”的场景,不适合做通用字典。
- 弱引用不等于”自动删除”。条目何时被清理取决于 GC 的实现,你不能依赖某个时间点去检查条目是否存在。
- 不能遍历 WeakMap 是有意为之:因为条目随时可能被 GC 清理,遍历结果不可靠。
- 原始值(number、string、boolean、null、undefined、symbol)不能作为 WeakMap 的键。
- WeakMap 的
get/set/has/delete时间复杂度接近 O(1)。 - Vue 3 的响应式系统用 WeakMap 存储原始对象到代理对象的映射(
targetMap),正是利用了 WeakMap 不阻止 GC 的特性。