必须用 weakmap 而非对象或 map,因其键为弱引用,节点被移除且无其他强引用时自动回收,避免 detached dom 和内存泄漏;weakmap 不可遍历、无 size、不能 clear,专用于绑定私有元数据。

用 WeakMap 给 DOM 节点关联额外数据,核心就一条:让数据生命周期自动跟随节点——节点被移除且无其他引用时,数据自动消失,不占内存。
为什么必须用 WeakMap,而不是对象或 Map
普通对象({})或 Map 会强持有 DOM 节点作为键,哪怕节点已从页面中调用 .remove() 或父元素清空了 innerHTML,只要它还在 Map 里,垃圾回收器(GC)就认为它“可达”,不会释放。久而久之,DevTools 的 Memory 面板会出现大量 detached DOM trees,JS 堆内存持续上涨。
WeakMap 的键是弱引用:它不计入 GC 的可达性判断。只要 DOM 节点失去所有强引用(比如从 DOM 移除 + 变量被覆盖/作用域退出),它就会被回收,对应条目在 WeakMap 中自然失效。
正确使用 WeakMap 的三步操作
确保安全、可靠、不报错:
-
创建单例 WeakMap 实例:全局只用一个,避免状态分散。例如:
const elementData = new WeakMap(); -
存数据前严格校验节点:WeakMap 的键必须是真实存在的
Node实例(如Element),不能是null、undefined、字符串 ID 或未挂载的动态节点。
推荐封装防护逻辑:function setData(node, value) {<br> if (node && node instanceof Node) {<br> elementData.set(node, value);<br> }<br>} -
读取时接受 undefined:节点可能已被回收,
get()返回undefined是正常行为,需主动判断:const data = elementData.get(btn);<br>if (data) { /* 安全使用 */ }
典型实战场景示例
给按钮记录点击次数(无泄漏):
const btn = document.querySelector('#btn');<br>const elementData = new WeakMap();<br><br>// 关联数据<br>elementData.set(btn, { clickCount: 0 });<br><br>btn.addEventListener('click', () => {<br> const data = elementData.get(btn);<br> if (data) {<br> data.clickCount++;<br> console.log(data.clickCount);<br> }<br>});<br><br>// 后续执行 btn.remove() → data 条目自动清理,无需手动 delete
容易忽略的关键细节
WeakMap 只保证键的弱引用,不保护值本身:
- 如果你把
elementData.get(node)返回的对象又赋值给全局变量、塞进定时器回调、或长期闭包里,这个值仍会被强持有,造成泄漏——WeakMap 不管这个。 - 值(value)的生命周期应尽量与节点对齐。例如避免在监听器中捕获整个 Vue 组件实例,而只取需要的字段。
- WeakMap 不可遍历、不可
size、不能clear,这是设计使然,不是缺陷。它本就不该用于“管理全部状态”,而是用于“按需绑定私有元数据”。











