WeakMap 避免内存泄漏的关键在于仅用外部生命周期可控的对象作键且不额外强持有,值不可反向引用键,适用于依附对象生命周期的场景,应统一复用单例实例。

WeakMap 本身不“管理”弱引用,它天然只支持弱引用键名——关键在于你如何用它。真正避免内存泄漏,靠的是理解它的行为边界,并避开那些看似合理实则埋雷的写法。
只让对象当键,且不额外强持有
WeakMap 的键必须是对象,这是硬性门槛,也是安全起点。但光满足类型还不够:如果这个对象在 WeakMap 外还有其他强引用(比如全局变量、闭包捕获、定时器回调里存着),那它就不会被回收,WeakMap 的“弱”也就没意义了。
- ✅ 正确做法:把 DOM 元素、组件实例这类生命周期由外部控制的对象直接当键,别再额外存一份引用
- ❌ 错误做法:先 let el = document.getElementById('x'); 再 weakMap.set(el, data); 然后又把 el 赋给 window.cachedEl —— 这样 el 就被 window 强持有了
值别拖住键,尤其警惕反向引用
WeakMap 只对键弱引用,值仍是强引用。如果值里存了对键本身的引用(比如 { owner: el }),就等于绕过 WeakMap 自己建了一条强链,GC 依然收不走键。
- ✅ 推荐值类型:简单对象、数字、布尔、函数(不闭包捕获键)、Symbol
- ❌ 避免值结构:{ node: el, handler: () => el.click() } —— handler 闭包捕获了 el,形成隐式强引用
- ⚠️ 注意:即使值只是普通对象,如果它后续被塞进 setInterval 或全局缓存,也会间接延长键的生命周期
用在“依附生命周期”的场景,别当通用缓存
WeakMap 不是 Map 的轻量替代品,它只适合一类需求:数据存在与否,完全跟随某个对象的存活状态。
- ✅ 典型用途:DOM 元素的状态标记(点击次数、禁用状态)、类的私有字段模拟、响应式系统中 effect 与 reactive 对象的绑定
- ❌ 不适合用途:需要主动清空的配置缓存、要遍历统计的活跃会话列表、依赖 size 判断容量的队列
- ? 提示:它没有 clear()、size、keys(),不是缺陷,而是设计使然——因为键随时可能消失,这些操作本就不具备确定语义
封装成模块级单例,别到处 new WeakMap()
每个 WeakMap 实例都是一份独立状态。如果不同模块各自 new 一个 WeakMap 来存同一类对象(比如都存 button),不仅浪费内存,还导致逻辑分散、调试困难。
- ✅ 推荐:统一导出一个 const elementState = new WeakMap(),所有相关逻辑复用它
- ❌ 多处定义:A 模块里 new WeakMap() 存按钮状态,B 模块里又 new 一个存同样按钮的样式信息 —— 键重复,值割裂,清理不可控


















