weakmap 是解决 spa 页面残留引用泄漏的天然工具,因其键为弱引用对象,dom 节点或组件实例销毁后可被 gc 自动回收,适用于缓存尺寸、绑定事件等附属元数据场景。

WeakMap 是解决 SPA 中已注销页面残留引用问题的天然工具——它允许你关联数据而不阻止垃圾回收。关键不在于“存”,而在于“不强留”。
为什么 WeakMap 能切断泄漏链
WeakMap 的键必须是对象,且对键的引用是弱引用:当 DOM 节点、组件实例等被移除或销毁,且外部再无其他强引用时,即使它仍作为 WeakMap 的键存在,GC 也能正常回收该对象及其关联值。
- 普通 Map 会持有键的强引用,导致节点即使从 DOM 移除、组件已卸载,仍无法释放
- WeakMap 不出现在 GC 根路径中,不会干扰标记-清除过程
- 适用于“附属元数据”场景,比如为某个 DOM 元素缓存尺寸、状态、绑定事件处理器等
典型泄漏场景与 WeakMap 替代方案
在大型 SPA 中,常见泄漏源如:路由切换后未清理的 DOM 缓存、第三方图表库绑定的私有状态、自定义 Hook 中保存的 ref 实例等。WeakMap 可安全承载这些临时关联:
-
替代全局 DOM 缓存:不用
const cache = new Map()存 DOM → 改用const sizeCache = new WeakMap(),以元素为键存宽高 - 管理组件私有状态:React/Vue 组件实例销毁后,其关联的动画帧 ID、ResizeObserver 实例可由 WeakMap 自动解绑
- 封装第三方库副作用:例如 ECharts 实例绑定的 DOM 容器,可用 WeakMap 记录初始化配置,避免因容器被 replaceChild 而残留引用
正确使用 WeakMap 的实践要点
WeakMap 本身不能遍历、不能清空、没有 size 属性——这不是缺陷,而是设计约束。使用时需注意:
- WeakMap 必须在模块作用域或闭包内创建,避免意外暴露;不要把它当作通用缓存
- 键只能是对象(如
element,componentInstance),不能是字符串或基本类型 - 值可以是任意类型,但若值本身又强引用了键(如闭包里捕获了 element),仍会造成泄漏
- 配合 DevTools 的 Detached DOM trees 快照筛选,能快速验证:泄漏节点是否仍在 WeakMap 中?若不在,说明已释放;若还在但没 Detached,说明引用关系健康
一个可落地的 Vue 组件示例
以下代码为滚动容器自动记录上次 scrollTop,避免因组件卸载后监听器未清除 + 缓存未释放导致泄漏:
const scrollCache = new WeakMap()
export default {
mounted(el) {
// 弱关联 DOM 与状态
scrollCache.set(el, { lastScroll: 0 })
const onScroll = () => {
const state = scrollCache.get(el)
if (state) state.lastScroll = el.scrollTop
}
el.addEventListener('scroll', onScroll)
// 清理函数仅需移除监听器,无需手动删 WeakMap 条目
el._cleanup = () => el.removeEventListener('scroll', onScroll)
},
unmounted(el) {
el._cleanup?.()
// WeakMap 条目随 el 自动消失,无需 scrollCache.delete(el)
}
}










