WeakMap 可避免发布订阅中监听器内存泄漏,因其键为对象且不阻止垃圾回收,事件源销毁时对应监听器集合自动释放;监听器应存于 Set 中并私有封装 WeakMap,取消订阅需显式删除。

WeakMap 可以在发布订阅模式中避免监听器内存泄漏,关键在于用它关联目标对象和监听器集合,让监听器随目标自动释放。
用 WeakMap 存储目标对象到监听器集合的映射
WeakMap 的键必须是对象,且不阻止垃圾回收。把事件源(如某个 DOM 元素、实例对象)作为键,对应一个 Set 或数组存监听函数,这样当事件源被销毁时,WeakMap 中该条目自动消失,监听器集合也随之可被回收。
- 不要把监听器本身作为 WeakMap 的键——监听器通常是函数,但 WeakMap 键必须是对象,且函数不是理想的弱引用目标;真正要“弱持有”的是事件源
- 监听器集合建议用 Set 而非数组,方便去重和高效删除
- WeakMap 实例应私有封装,不暴露给外部,防止误操作破坏弱引用语义
订阅时:将监听器加入 WeakMap 关联的集合
调用 on(target, event, listener) 时,先检查 WeakMap 是否已有该 target 对应的监听器集合;没有就新建一个 Set 并 set 进 WeakMap;然后把 listener 加入该 Set。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
const listenersMap = new WeakMap();
function on(target, type, listener) {
if (!listenersMap.has(target)) {
listenersMap.set(target, new Set());
}
listenersMap.get(target).add({ type, listener });
}
触发与清理:只操作 WeakMap 中存在的目标
emit 时遍历 WeakMap 中所有已注册的目标不现实(WeakMap 不支持遍历),所以 emit 必须传入具体 target;再通过 listenersMap.get(target) 获取监听器集合,过滤 type 后执行。一旦 target 被 GC,get 返回 undefined,自然跳过,无需手动清理。
立即学习“Java免费学习笔记(深入)”;
- 取消订阅(off)仍需显式调用,从对应 Set 中 delete 监听器;WeakMap 不影响 Set 内部引用,不删会持续占用内存
- 不推荐在 target 构造函数里自动绑定 this —— 若监听器是箭头函数或 bind 过的函数,它可能隐式持有 target 引用,反而阻碍 target 回收
- 若需支持通配符或全局监听,应另用普通 Map 管理,与 WeakMap 分离,避免污染弱引用边界
注意 WeakMap 的局限性
WeakMap 不能枚举、不能 clear、不能 forEach,这恰是它适合生命周期管理的原因:你无法意外保留对键的强引用。但它也意味着调试困难——无法 console.log 查看当前所有监听关系。
- 开发阶段可临时用 Map 替代 WeakMap 方便调试,上线前换回
- 若监听器需要长期存在(比如全局状态监听),不应依赖 WeakMap 绑定到某个易销毁对象上
- WeakMap 不解决闭包导致的内存泄漏,监听器内部若捕获了大对象,仍需自行管理

















