选用Map实现事件总线,因其支持任意类型键、无原型污染、size可查、has/delete语义明确;on注册回调并返回取消函数,emit触发时拷贝执行并try/catch隔离错误,off精准删除,once通过自清理wrapper实现单次订阅。

用 Map 实现事件总线,核心是把事件名作为键、回调函数数组作为值来组织数据。相比普通对象,Map 支持任意类型作键(比如 Symbol 或函数),键名不会被意外覆盖,且天然可遍历、大小可查,更适合做事件注册中心。
为什么选 Map 而不是普通对象?
普通对象的 key 只能是字符串或 Symbol,且原型链上自带属性(如 toString),容易误判;而 Map 是纯净的键值容器,不存在属性污染问题。另外,Map 的 size 属性直接返回订阅数,has() 方法判断是否存在某事件更可靠,删除用 delete() 也比 delete obj[key] 更语义清晰。
on / subscribe:注册监听器
每次调用 on(type, fn),先用 get(type) 查该事件是否已有回调数组;没有就新建一个空数组并 set(type, [fn]),有则直接 push(fn)。关键点是返回一个取消订阅函数:
- 这个函数内部调用
off(type, fn),确保能精准移除当前注册的那一个回调 - 避免用
splice(index, 1)时因数组变化导致索引错位,推荐用filter或findIndex + splice配合严格相等判断
emit:触发所有监听器
执行 emit(type, ...args) 时,先 get(type) 拿到回调数组;如果数组为空或不存在,直接返回 false(符合力扣 2694 接口要求);否则遍历执行每个回调,并把 ...args 原样传入:
- 用
for...of或forEach都可以,但注意不要在遍历时修改原数组(比如once内部取消自身)——建议先拷贝一份副本再执行 - 对每个回调加
try/catch包裹,防止某个 handler 报错中断其余执行
off 和 once:控制生命周期
off(type, fn) 是精准卸载:获取该 type 对应数组,用 indexOf 或 findIndex 找到 fn 位置后 splice 删除;若不传 fn,就直接 delete 整个键。
once(type, fn) 的本质是“注册一个带自清理逻辑的 wrapper 函数”:
- 定义一个内部函数
handler,它先调用off(type, handler),再执行原始fn - 把这个
handler传给on(type, handler)完成注册 - 这样第一次
emit就会自动解除绑定,无需手动管理
不复杂但容易忽略。











