用 Set 管理事件监听器因其自动去重、add 方法幂等、语义清晰且性能优于需手动查重的数组;需使用同一函数引用,支持扩展为 Map<string, Set> 实现多事件类型,WeakSet 不适用于 emit 场景。

用 Set 管理事件监听器,核心是利用其自动去重特性,避免重复添加同一监听函数,同时方便统一移除。
为什么不用数组而用 Set?
数组需手动检查是否已存在该监听器(比如用 indexOf 或 findIndex),逻辑繁琐且易出错;Set 的 add() 方法天然幂等——重复添加同一个函数引用不会产生新元素,语义清晰、性能更好。
基础实现:封装一个支持去重的事件管理器
以下是一个轻量级示例,基于 Set 存储监听器,并提供 on、off、emit 方法:
class EventEmitter {
constructor() {
this.listeners = new Set();
}
on(fn) {
this.listeners.add(fn); // 自动忽略重复 fn 引用
}
off(fn) {
this.listeners.delete(fn);
}
emit(...args) {
this.listeners.forEach(fn => fn(...args));
}
}
使用时注意:必须传入**同一个函数引用**才能正确增删。匿名函数或箭头函数每次都会新建引用,无法被识别为“重复”或成功删除。
立即学习“Java免费学习笔记(深入)”;
实际应用中的关键细节
-
监听器必须是具名函数或变量保存的函数:
✅ 正确:
const handler = () => console.log('ok'); em.on(handler); em.off(handler);❌ 错误:em.on(() => console.log('ok'));—— 无法用off移除 -
若需支持事件类型(如 'click'、'data'),可扩展为 Map<string, Set>:
每个事件类型对应一个独立
Set,互不干扰,依然保持去重能力 -
兼容原生 DOM 元素?可以,但需自行绑定:
Set本身不操作 DOM,你可包装addEventListener,在内部用Set记录已添加的回调,防止多次调用addEventListener同一函数
和 WeakSet 的区别?什么时候考虑它?
WeakSet 不能遍历、不阻止垃圾回收,不适合做事件管理器主体(因为 emit 需要遍历所有监听器)。但可用于辅助场景,比如标记“已绑定”的 DOM 元素,避免重复初始化,此时无需长期持有引用。


















