WeakMap 能让监听器自动随订阅对象销毁而释放,避免手动注销遗漏造成的内存泄漏;因其对 key 是弱引用,当订阅对象仅被 WeakMap 引用时可被垃圾回收,而普通 Map 持有强引用会阻止 GC,导致内存泄漏。

WeakMap 能让监听器自动随订阅对象销毁而释放,避免手动注销遗漏造成的内存泄漏。
为什么普通 Map 会导致内存泄漏
发布订阅模式中,如果用普通 Map 存储对象 → 监听器的映射,即使订阅对象(如某个 DOM 元素或组件实例)已被业务逻辑丢弃,只要 Map 还持有它的强引用,垃圾回收器就无法回收该对象。监听器函数又常闭包捕获了外部变量,进一步延长整个作用域链的存活时间。
- 订阅者对象被设为 null 或离开作用域,但 Map 里仍存着 key,对象无法 GC
- 监听器函数未显式调用 unsubscribe,残留引用链持续存在
- 尤其在单页应用中,频繁创建/销毁组件时,泄漏会快速累积
WeakMap 的天然优势
WeakMap 的 key 必须是对象,且对 key 是弱引用 —— 当该对象仅被 WeakMap 引用时,它可被垃圾回收。监听器可以安全地挂在 WeakMap 上,无需手动清理 key。
- key 是订阅目标(如 eventTarget、Vue 组件实例、React class 组件 this)
- value 可以是监听器数组,或一个封装了回调与清理逻辑的对象
- 一旦订阅目标被销毁,WeakMap 自动“忘记”它,关联监听器自然失活
用 WeakMap 实现可自动清理的事件总线
下面是一个轻量但实用的实现示例:
立即学习“Java免费学习笔记(深入)”;
class AutoCleanupEventBus {
constructor() {
// key: 订阅对象(如组件实例),value: { type → [handlers] }
this.listenerMap = new WeakMap();
}
on(target, type, handler) {
if (!this.listenerMap.has(target)) {
this.listenerMap.set(target, new Map());
}
const typeMap = this.listenerMap.get(target);
if (!typeMap.has(type)) {
typeMap.set(type, []);
}
typeMap.get(type).push(handler);
}
emit(target, type, ...args) {
const typeMap = this.listenerMap.get(target);
if (typeMap && typeMap.has(type)) {
typeMap.get(type).forEach(fn => fn(...args));
}
}
// 可选:提供显式 off,但不依赖它来防泄漏
off(target, type, handler) {
const typeMap = this.listenerMap.get(target);
if (typeMap && typeMap.has(type)) {
const handlers = typeMap.get(type);
const i = handlers.indexOf(handler);
if (i > -1) handlers.splice(i, 1);
}
}
}
// 使用示例(React class 组件中)
class MyComponent extends React.Component {
bus = new AutoCleanupEventBus();
componentDidMount() {
// target 是 this,WeakMap key 弱引用,组件卸载后自动清理
this.bus.on(this, 'data-update', this.handleUpdate);
}
handleUpdate = (data) => {
this.setState({ data });
};
render() {
return <div>{this.state.data}</div>;
}
}
注意:WeakMap 不能遍历,也不暴露 key,所以无法主动扫描“已失效 target”。但这恰是优点 —— 它不阻止 GC,也不需要你轮询清理。
适用场景与注意事项
WeakMap 方案适合 target 明确、生命周期由框架或业务控制的对象(如组件实例、DOM 元素、自定义类实例)。不适合字符串、数字等基本类型作 key,也不能用于全局事件(如 window 级监听)。
- 确保传入 WeakMap 的 target 确实是你想绑定生命周期的那个对象
- 避免把匿名函数多次重复注册,否则无法精确 off(但泄漏风险仍低)
- 若需支持函数式组件,可用 useRef 创建稳定对象作为 target,再传给 on
- WeakMap 不解决监听器内部闭包导致的泄漏,需同时注意 handler 自身是否持有了长生命周期引用


















