WeakMap 是实现 DOM 节点元数据“非侵入式”关联的唯一原生可靠方案:它不污染节点属性或 dataset,节点移除后元数据自动消失;因其键为弱引用,可避免内存泄漏,而 Map 或普通对象会阻止 GC 回收。

WeakMap 是实现 DOM 节点元数据“非侵入式”关联的唯一原生可靠方案:它不往节点上挂属性、不污染 dataset 或原型链,且节点被移除后,对应元数据自动消失,无需手动清理。
为什么叫“非侵入式”?
传统方式如 el.__state = {...} 或 el.dataset.custom = "value" 都属于侵入——前者污染对象属性、易被误删或覆盖;后者受限于字符串类型、语义模糊、且可能被 HTML 操作(如 innerHTML 重写)意外清空。WeakMap 完全绕过 DOM 节点自身,用外部映射“悄悄绑定”,节点对此毫无感知。
必须用 WeakMap,不能用 Map 或对象
- Map 和普通对象会对 DOM 节点保持强引用:哪怕节点已从页面移除、JS 里也没其他变量指向它,只要 Map 还存着它,GC 就无法回收——内存泄漏就发生了
- WeakMap 的键是弱引用:只要节点在全局/作用域中没有其他强引用,即使 WeakMap 里还“记着”,GC 也能照常回收节点,同时该条目自动失效
- WeakMap 键只能是对象(DOM 节点天然符合),值可以是任意类型(对象、函数、数字等)
推荐封装成可复用的元数据管理器
避免每个模块都 new WeakMap(),应统一导出一个模块级单例,并提供清晰语义的接口:
const elementMeta = new WeakMap();
export function setMeta(node, key, value) {
if (!(node instanceof Node)) return;
if (!elementMeta.has(node)) {
elementMeta.set(node, Object.create(null));
}
elementMeta.get(node)[key] = value;
}
export function getMeta(node, key) {
return elementMeta.has(node) ? elementMeta.get(node)[key] : undefined;
}
export function deleteMeta(node, key) {
if (elementMeta.has(node)) {
delete elementMeta.get(node)[key];
}
}
- 检查
node instanceof Node防止传入非法值 - 用
Object.create(null)创建纯净对象,避免原型污染 - 读取前始终用
has()判断,因为节点可能已被移除,get()会返回undefined
关键注意事项:WeakMap 不是万能保险
WeakMap 只解决它自己这一路的引用问题。以下操作仍会导致节点无法回收,和 WeakMap 是否使用无关:
- 事件监听器中直接捕获节点:
el.addEventListener('click', () => console.log(el)) - 把节点存进全局数组或 Map:
window.allNodes.push(el)或cache.set(el, data) - 在闭包中长期持有节点引用,比如返回一个绑定了 el 的函数
- 将节点赋值给 window 属性或全局变量
这些都会形成额外的强引用,“压垮”WeakMap 的弱引用机制。










