用Map存储事件名与监听器列表更安全灵活:支持任意类型键、避免冲突与污染、准确计数、遍历可靠;核心是事件名为Map键、监听器数组为值,注册时初始化数组,触发时浅拷贝执行,移除时splice并可清理空数组。

在事件总线(Event Bus)中,用 Map 存储事件名与监听器列表,比用普通对象更安全、更灵活:它支持任意类型作为键(比如 Symbol、函数、对象),避免属性名冲突和原型污染,且能准确反映键值对数量,遍历也更可靠。
用 Map 管理事件名 → 监听器数组的映射
核心思路是:每个事件名(字符串或 Symbol)作为 Map 的键,对应一个数组,存放所有注册的监听函数。
- 注册监听器时,检查该事件是否已存在;不存在就新建空数组并 set 进 Map
- 触发事件时,从 Map 中 get 对应数组,遍历执行(注意处理监听器被中途移除的情况)
- 移除监听器时,需找到并 splice 掉目标函数(不能只用 filter,否则影响原数组引用)
基础实现示例
以下是一个轻量、可直接使用的事件总线类:
class EventBus {
constructor() {
this.events = new Map(); // key: eventName, value: listener array
}
on(eventName, listener) {
if (!this.events.has(eventName)) {
this.events.set(eventName, []);
}
this.events.get(eventName).push(listener);
}
emit(eventName, ...args) {
const listeners = this.events.get(eventName);
if (listeners && listeners.length) {
// 浅拷贝避免执行中修改原数组导致跳过或重复
[...listeners].forEach(fn => fn(...args));
}
}
off(eventName, listener) {
const listeners = this.events.get(eventName);
if (listeners) {
const index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
// 可选:空数组时清理键,节省内存
if (listeners.length === 0) {
this.events.delete(eventName);
}
}
}
}
进阶考虑:支持一次性监听与通配符
真实项目中常需扩展功能:
立即学习“Java免费学习笔记(深入)”;
-
once(一次性监听):内部调用
on后,在监听器执行完立即调用off -
通配符匹配(如
"user.*"):需额外维护一个“模式监听器”结构,或用Array.from(map.entries())遍历筛选,不建议高频场景下滥用 -
监听器上下文绑定:
on可接受第三个参数thisArg,内部用fn.bind(thisArg)存储
注意事项与避坑点
Map 本身不自动去重,相同函数多次 on 会存多次——这是合理行为(允许同一函数多次订阅)。如需防重复,注册前可用 includes() 检查。
- 监听器执行出错不应阻断后续监听器,建议用
try/catch包裹单个调用 - 避免在监听器中同步调用
off当前正在执行的函数(虽可行,但易引发逻辑混乱) - 若事件名是动态生成的对象或 Symbol,确保每次 emit 用的是同一个引用,否则
get失败


















