实现发布订阅模式
手写 Event Bus / EventEmitter
问题
实现一个发布订阅模式(Event Bus / EventEmitter),支持事件的订阅、取消订阅、触发和一次性订阅。
解答
class EventEmitter {
constructor() {
// 存储事件和对应的回调函数列表
this.events = {};
}
// 订阅事件
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
return this; // 支持链式调用
}
// 取消订阅
off(event, callback) {
if (!this.events[event]) return this;
if (!callback) {
// 没传 callback,移除该事件所有监听器
delete this.events[event];
} else {
// 移除指定的回调
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
return this;
}
// 触发事件
emit(event, ...args) {
if (!this.events[event]) return this;
this.events[event].forEach(callback => {
callback.apply(this, args);
});
return this;
}
// 只订阅一次
once(event, callback) {
// 包装函数,执行后自动移除
const wrapper = (...args) => {
callback.apply(this, args);
this.off(event, wrapper);
};
this.on(event, wrapper);
return this;
}
}
使用示例
const emitter = new EventEmitter();
// 订阅事件
function handleMessage(msg) {
console.log('收到消息:', msg);
}
emitter.on('message', handleMessage);
emitter.on('message', (msg) => console.log('另一个监听器:', msg));
// 触发事件
emitter.emit('message', 'Hello World');
// 输出:
// 收到消息: Hello World
// 另一个监听器: Hello World
// 取消订阅
emitter.off('message', handleMessage);
emitter.emit('message', '第二条消息');
// 输出:
// 另一个监听器: 第二条消息
// 一次性订阅
emitter.once('login', (user) => console.log(user, '登录了'));
emitter.emit('login', '张三'); // 输出: 张三 登录了
emitter.emit('login', '李四'); // 无输出,监听器已移除
关键点
- 用对象存储事件名到回调数组的映射
off需要处理两种情况:移除全部监听器和移除指定监听器once通过包装函数实现,执行后调用off移除自身- 返回
this支持链式调用 emit使用apply传递参数,保证this指向正确
目录