weakmap 是唯一能配合垃圾回收实现“实例销毁即状态释放”的私有存储方案,因其键为弱引用、不阻止 gc、不可枚举且键必须为对象;标准用法是模块级单例 weakmap,以 this 为键延迟存取私有状态。

WeakMap 是 JavaScript 中唯一能真正配合垃圾回收机制、实现“实例销毁即状态释放”的私有状态存储方案。它不阻止 GC,只要类实例失去所有强引用,对应条目就会被自动清理,无需手动 delete 或 dispose。
为什么 WeakMap 能做到自动回收
WeakMap 的键是弱引用的——它不计入 JavaScript 引擎的引用计数。当一个对象(比如类实例)只被 WeakMap 作为 key 持有时,一旦该对象在其他地方不再被任何变量、闭包或 DOM 引用,引擎就会在下一次垃圾回收时将其连同 WeakMap 中的值一并清除。
- 普通 Map 或对象属性会形成强引用,导致实例无法被回收,造成内存泄漏
- WeakMap 的键必须是对象(不能是字符串/数字),天然适配类实例、DOM 节点等引用类型
- WeakMap 不可枚举、无 keys() / values() 方法,从设计上就保障了私有性
标准写法:模块级单例 + 实例为键
在模块顶层定义一个 WeakMap 实例,所有类共享它;每次操作都以 this(即当前实例)为 key:
const privateStore = new WeakMap();
class Button {
constructor(label) {
this.label = label;
// ✅ 延迟绑定:不预设,只在首次需要时 set
}
get state() {
let priv = privateStore.get(this);
if (!priv) {
priv = { clicks: 0, lastClickAt: 0 };
privateStore.set(this, priv); // 首次访问才创建
}
return priv;
}
click() {
this.state.clicks++;
this.state.lastClickAt = Date.now();
}
}
- 不要在 constructor 里提前 set —— 否则不是“延迟”,且可能初始化无用状态
- 每次 get 前先 has() 判断,避免对已销毁实例调用 get() 返回 undefined 后报错
- value 推荐用普通对象,方便后续增删属性;避免频繁 replace 整个 value
避坑要点:防止间接强引用
WeakMap 本身不会泄漏,但常见错误会让实例意外“活”下来:
- 事件监听器中箭头函数捕获 this:
el.addEventListener('click', () => this.handleClick())→ this 被闭包强持有 - 把实例存进全局数组、Set 或 Promise 链中
- 用 setTimeout / setInterval 回调引用 this,且未 clearTimeout
- 将实例作为参数传给长期存活的第三方库或框架缓存
验证是否生效:在 Chrome DevTools 的 Memory 面板中录制堆快照,筛选构造函数名,确认销毁后对应实例和 WeakMap 条目同步消失。
进阶:结合惰性计算与自动清理
若私有状态依赖高成本操作(如解析大 JSON、测量 DOM 尺寸),可在 WeakMap 中存一个 getter 函数或 Promise,在首次访问时执行并缓存结果:
get dimensions() {
let priv = privateStore.get(this);
if (!priv?.dimensions) {
priv = priv || {};
priv.dimensions = Promise.resolve()
.then(() => this.el.getBoundingClientRect())
.then(rect => ({ width: rect.width, height: rect.height }));
privateStore.set(this, priv);
}
return priv.dimensions;
}
这样既避免重复计算,又保持弱引用特性——Promise 和结果对象随实例一起被回收。










