WeakMap和WeakSet通过弱引用使键值随对象自动回收,但需遵守单例封装、值不反向引用、节点挂载完成三铁律;非遍历/统计场景勿替代Map/Set。

WeakMap 和 WeakSet 不是“防止”内存泄漏的开关,而是通过弱引用机制让数据生命周期自动跟随对象——用对了,GC 就能及时回收;用错了,照样卡内存。
WeakMap:用对象当键,存私有状态
它只接受真实对象作键(DOM 元素、类实例等),且对键是弱引用。只要这个对象在别处没了强引用,GC 就能把它连同 WeakMap 里的对应条目一起清理掉。
- ✅ 正确做法:把 DOM 节点当键,值只放简单数据,比如 { clicked: true, lastTime: Date.now() }
- ❌ 危险操作:值里存了闭包回调或反向引用节点(如 { owner: node }),这会让节点无法被回收
- ⚠️ 注意:键不能是字符串、数字或 null;document.getElementById() 返回 null 时直接 set 会报错,得先判断 node && node instanceof Node
WeakSet:标记已处理节点,不阻断回收
适合做“已初始化”“已监听”这类一次性标记。节点被移除后,只要没其他强引用,WeakSet 里的记录也会自动消失。
- ✅ 示例:事件代理中用 WeakSet 记录哪些元素已绑定过懒加载逻辑,后续不再重复处理
- ❌ 错误用法:拿它当普通 Set 用来统计数量或遍历——它没有 size、不能 for...of,也没 keys()
- ⚠️ 提醒:WeakSet 只存对象,不能存原始值;也不能靠它做缓存淘汰或批量清理
必须遵守的三条铁律
WeakMap/WeakSet 的弱引用能力,只有在规范使用下才生效:
立即学习“Java免费学习笔记(深入)”;
- 单例封装:WeakMap 实例必须在模块顶层声明一次(const elementState = new WeakMap()),别在 class 构造函数里每个实例都 new 一个
- 值不反向牵制键:值里不能持有对键的强引用,也不能被全局变量、定时器、未解绑事件长期持有
- 动态节点要等挂载完成:用 node.isConnected 确保节点已真实插入 DOM 再存入,避免存了个“半成品”
什么场景别硬套 WeakMap/WeakSet
它们不是万能补丁,也不该替代 Map/Set:
- 需要遍历、统计、清空或调试——选 Map 或 Set
- 要缓存计算结果并手动控制过期——用 Map + setTimeout/delete
- 键可能是字符串或数字——WeakMap 直接拒绝,只能换方案


















