WeakMap 和 WeakSet 的核心价值是用弱引用切断对象与数据的强绑定,使垃圾回收器能正常清理已脱离业务逻辑的对象;它们适用于DOM节点私有状态存储、节点初始化标记及解耦闭包循环引用等场景,但不可遍历、无size、不支持clear,且键只能为对象。

WeakMap 和 WeakSet 的核心价值,就是用弱引用切断对象与数据之间的强绑定,让垃圾回收器能正常清理那些已脱离业务逻辑的对象。它们不解决所有内存泄漏,但对“对象+附属数据”这类场景特别有效。
用 WeakMap 存储 DOM 节点的私有状态
当你需要为某个 DOM 元素记录点击次数、加载状态或配置项时,别直接挂在节点上(如 el.__cache = {...}),也别用普通 Map 全局缓存——这都会形成强引用,阻止节点被回收。
- 创建一个全局 WeakMap 实例,专门用于关联节点和元数据
- 键必须是 DOM 元素(或其他对象),值可以是任意类型(对象、函数、原始值)
- 一旦元素从 DOM 中移除且无其他 JS 引用,它连同 WeakMap 中对应的键值对会自动消失
示例:
const nodeMeta = new WeakMap();function markAsLoaded(el) {
nodeMeta.set(el, { loaded: true, timestamp: Date.now() });
}
function isLoaded(el) {
return nodeMeta.get(el)?.loaded;
}
用 WeakSet 标记已初始化或已监听的节点
在懒加载、事件代理或组件挂载逻辑中,常需判断某个节点是否已被处理。若用 Set 存储,节点删了还在集合里,数据就滞留内存。
- WeakSet 只接受对象,add 进去后不会延长其生命周期
- 调用
has()可快速判断该对象是否还“活着”且曾被标记过 - 适合做“一次性操作”的守门人,比如只给某节点绑一次 click 事件
示例:
const initialized = new WeakSet();function initOnce(el) {
if (!initialized.has(el)) {
initialized.add(el);
el.addEventListener('click', handleUserAction);
}
}
解耦闭包中的循环引用
事件处理器常通过闭包捕获外部变量,而这些变量又可能持有 DOM 节点,形成“节点 → 闭包 → 节点”的循环引用。GC 在某些情况下无法及时识别并释放。
- 把本该闭包持有的上下文数据,改存到 WeakMap 中,键设为触发事件的节点
- 回调函数内只通过 WeakMap.get(this) 拿数据,不直接闭包捕获节点或大对象
- 这样即使节点被移除,闭包本身不再强持节点,循环被打破
示例:
const handlerData = new WeakMap();function setupCounter(el) {
handlerData.set(el, { count: 0 });
el.onclick = function () {
const data = handlerData.get(this);
if (data) data.count++;
};
}
注意事项和限制
WeakMap/WeakSet 不是万能胶布,用错地方反而更难调试:
- 不能遍历、没有
size、不支持clear(),所以不适合做需要统计或批量操作的容器 - 键只能是对象,字符串、数字等原始值会报错
- 无法确认某条数据是否已被 GC 清理,调试时看不到内容,得靠逻辑推断或内存快照验证
- 它们不是 Map/Set 的替代品,而是补充——强引用该强,弱引用该弱,各司其职


















