标准答案
- 观察者模式通常是被观察者直接维护观察者列表,状态变化时直接通知观察者。被观察者和观察者之间存在直接关系。
- 发布订阅模式中间多了一个事件中心。发布者只负责发布事件,订阅者只负责订阅事件,双方不需要直接知道彼此,因此解耦更强。
- EventEmitter 的核心结构是事件名到监听器集合的映射。on 注册监听器,off 移除监听器,emit 按事件名触发监听器并传递参数,once 注册只执行一次的监听器。
- 实现时要处理多个监听器、重复注册、解绑引用、once 包装函数、emit 过程中增删监听器等边界。更完整的版本还会处理错误事件、最大监听数量和异步监听器。
题目解析
观察者模式更像“对象直接通知自己的观察者”,发布订阅更像“所有消息先经过事件中心”。这也是它们在耦合程度上的主要差异。
Map<string, Set<Function>> 是比较直接的数据结构。Map 保存事件名,Set 保存监听器,Set 能避免同一个函数被重复加入同一个事件。
emit 时最好遍历监听器快照,而不是直接遍历原集合。这样监听器内部执行 off 或 on 时,不会影响本轮事件触发的遍历结果。
代码示例
下面是一个包含 on、off、emit、once 的核心版 EventEmitter。
JavaScript
class EventEmitter {
constructor() {
this.events = new Map()
}
on(type, handler) {
const handlers = this.events.get(type) || new Set()
handlers.add(handler)
this.events.set(type, handlers)
return () => this.off(type, handler)
}
off(type, handler) {
const handlers = this.events.get(type)
if (!handlers) return
for (const item of handlers) {
if (item === handler || item.original === handler) {
handlers.delete(item)
}
}
if (handlers.size === 0) {
this.events.delete(type)
}
}
emit(type, ...args) {
const handlers = this.events.get(type)
if (!handlers) return false
Array.from(handlers).forEach(handler => handler(...args))
return true
}
once(type, handler) {
const wrapper = (...args) => {
this.off(type, wrapper)
handler(...args)
}
wrapper.original = handler
return this.on(type, wrapper)
}
}常见误区
- 只实现 on/emit,漏掉 off 和 once。
- once 包装后无法正确 off 原回调。
- emit 时直接遍历原数组,导致回调中增删监听器影响当前遍历。