WeakMap 是唯一能安全绑定 DOM 节点私有状态且不引发内存泄漏的原生方案,因其键为弱引用,节点被移除后自动失效,无需手动清理;普通对象或 Map 会强引用节点导致内存泄漏;WeakMap 键必须为对象(如 DOM 节点),传入原始值会抛错;应封装为模块单例并校验键类型;其值仍需避免被闭包长期持有,且不可遍历或获取 size。
WeakMap 是唯一能安全绑定 DOM 节点私有状态且不引发内存泄漏的原生方案。 它不阻止节点被 GC 回收,只要节点从 DOM 中移除、又没被其他 JS 变量强引用,对应条目就自动失效——你不用手动 delete,也不用监听 DOMNodeRemoved(已废弃)或 MutationObserver 做清理。
普通对象({})或 Map 对键是强引用:哪怕节点早已被 remove() 或父元素 innerHTML = '' 清空,只要它还“挂”在 Map 里,GC 就无法回收该节点及其关联的整个 DOM 子树。
performance.memory.usedJSHeapSize 持续上涨,DevTools 的 Memory 面板显示大量 detached DOM treesconst cache = new Map(); cache.set(document.getElementById('btn'), { loading: true });
cache 仍持有对它的强引用,内存永远不释放WeakMap 的 set() 方法只接受对象作键,DOM 节点天然符合;但若误传字符串 ID、数字索引或 null,会直接抛出 TypeError: Invalid value used as weak map key。
weakMap.set(btn, { count: 0, pending: false });
weakMap.set('btn-1', ...)、weakMap.set(42, ...)、weakMap.set(null, ...)
document.querySelector() 返回 null 时,必须先判空再 set,否则报错不要每次都在函数内新建 WeakMap,应统一导出一个实例,并在 set 前校验键是否为真实 DOM 节点——避免把普通对象、Window、Array 等误当节点传入。
const nodeState = new WeakMap();function setNodeState(node, state) { if (!(node instanceof Node)) return; nodeState.set(node, { ...nodeState.get(node), ...state });}function getNodeState(node) { return nodeState.get(node) ?? null;}// 使用const btn = document.querySelector('button');setNodeState(btn, { disabled: true });console.log(getNodeState(btn)); // { disabled: true }
nodeState.get(btn) 返回的对象),那值不会被回收——WeakMap 只管键的弱引用,不管值真正容易被忽略的是:WeakMap 不提供 size、不支持 keys() 遍历,也没法调试查看所有条目。这不是缺陷,而是设计使然——防止你无意中通过遍历延长节点生命周期。所以别试图“监控”它,只把它当成一条单向绑定通道:节点活着,数据可用;节点没了,数据自然消失。