用一个实验搞懂 WeakMap 到底"弱"在哪里
Map 持有 key 的强引用,忘了删就永远不释放。WeakMap 持有 key 的弱引用,key 没人用了就自动回收,entry 跟着消失。但"自动回收"到底是怎么自动的?
实验设计
同时往 Map 和 WeakMap 里各塞一条数据:
const strongMap = new Map()
const weakMap = new WeakMap()let keyA = { label: 'Map-key' }
let keyB = { label: 'WeakMap-key' }strongMap.set(keyA, new BigData('MapValue'))
weakMap.set(keyB, new BigData('WeakMapValue'))
然后断开外部引用:
keyA = null
keyB = null
问题来了:Map 和 WeakMap 里的数据会怎样?
怎么知道对象被 GC 了
JavaScript 有个 FinalizationRegistry,对象被垃圾回收时会触发回调:
const finalizer = new FinalizationRegistry((label) => {console.log(`${label} 被 GC 了`)
})const key = { name: 'test' }
finalizer.register(key, '这个是 test key')key = null // 断开引用,等 GC 回收后回调触发
思路很清楚:分别给 Map 的 key 和 WeakMap 的 key 注册 finalizer,看谁的回调触发了、谁的没有。
实验结果
第一步:初始状态

第二步:点击添加 entry
往 Map 和 WeakMap 各插入一条数据,两边都注册 FinalizationRegistry。

Map.size = 1,WeakMap 插入成功。
第三步:点击断开 key 引用
把外部变量置 null。Chrome 拍快照时会自动触发一轮 GC,所以这一步同时完成了断开引用和触发 GC。看日志面板:
[Finalizer] WeakMap 的 key "WeakMap-key" 被 GC 了!entry 已自动移除

只有 WeakMap 的 finalizer 触发了。Map 的没动静。用 Heap Snapshot 搜类名确认一下:
- 搜
MapValue→ 还在 - 搜
WeakMapValue→ 没了
为什么会这样
keyA = null 之后,各自的引用链长这样:
Map:strongMap → keyA (强引用) → BigDatakeyA 还被 Map 拽着,GC 认为它可达,不回收WeakMap:weakMap ➜ keyB (弱引用) → BigData弱引用不算"可达",GC 认为 keyB 不可达,回收key 被回收,entry 自动消失,value 也跟着被回收
"弱引用"的"弱"就在这:GC 判断对象是否存活时,不把 WeakMap 的引用算进去。对象如果没有其他强引用,就回收,WeakMap 管不住。
顺带解释一个相关问题:为什么 WeakMap 连 .size 都没有?
Map 给你 .size,因为 entry 是确定性的——你不删就不会少。WeakMap 的 entry 随时可能被 GC 回收,给你一个数字没意义,下一秒可能就变了。同理也没有 .keys()、.values()、.entries()。你能做的只有 get()、set()、has()、delete(),而且必须拿着 key 对象才能操作。
实际场景
拿 React 举例,你想缓存组件实例对应的数据:
// 用 Map:组件卸载后数据永远不释放
const cache = new Map()
cache.set(componentInstance, hugeData)
// 忘记 cache.delete(componentInstance) → 泄漏// 用 WeakMap:组件被 GC 后缓存自动消失
const cache = new WeakMap()
cache.set(componentInstance, hugeData)
// 不需要手动清理,组件没了缓存就没了
凡是"缓存的生命周期跟 key 对象一致"的场景,用 WeakMap 就不用操心清理的事。
总结
| Map | WeakMap | |
|---|---|---|
| key 引用类型 | 强引用 | 弱引用 |
| key 被 GC | 不会(Map 拽着) | 会(弱引用不算可达) |
| .size | 有 | 没有 |
| 遍历 | 可以 | 不可以 |
| 忘记清理 | 内存泄漏 | 自动回收 |
| key 类型 | 任意 | 只能是对象 |
