实现发布订阅模式
手写实现一个完整的发布订阅模式(EventEmitter),支持事件的订阅、发布、取消订阅等功能
问题
发布订阅模式是一种常见的设计模式,用于实现对象间的松耦合通信。需要实现一个事件管理器,支持以下功能:
- 订阅事件(on):注册事件监听器
- 发布事件(emit):触发事件并传递参数
- 取消订阅(off):移除指定的事件监听器
- 单次订阅(once):事件触发一次后自动取消订阅
- 清空事件(clear):清空指定事件的所有监听器
解答
class EventEmitter {
constructor() {
// 存储事件及其对应的监听器列表
this.events = {};
}
/**
* 订阅事件
* @param {string} eventName - 事件名称
* @param {Function} callback - 回调函数
*/
on(eventName, callback) {
if (typeof callback !== 'function') {
throw new TypeError('callback must be a function');
}
// 如果事件不存在,初始化为空数组
if (!this.events[eventName]) {
this.events[eventName] = [];
}
// 将回调函数添加到事件监听器列表
this.events[eventName].push(callback);
return this; // 支持链式调用
}
/**
* 发布事件
* @param {string} eventName - 事件名称
* @param {...any} args - 传递给回调函数的参数
*/
emit(eventName, ...args) {
// 如果事件不存在或没有监听器,直接返回
if (!this.events[eventName] || this.events[eventName].length === 0) {
return false;
}
// 执行所有监听器
this.events[eventName].forEach(callback => {
callback(...args);
});
return true;
}
/**
* 取消订阅
* @param {string} eventName - 事件名称
* @param {Function} callback - 要移除的回调函数
*/
off(eventName, callback) {
// 如果事件不存在,直接返回
if (!this.events[eventName]) {
return this;
}
// 如果没有指定回调函数,移除该事件的所有监听器
if (!callback) {
delete this.events[eventName];
return this;
}
// 过滤掉指定的回调函数
this.events[eventName] = this.events[eventName].filter(
cb => cb !== callback && cb.origin !== callback
);
return this;
}
/**
* 单次订阅(触发一次后自动取消)
* @param {string} eventName - 事件名称
* @param {Function} callback - 回调函数
*/
once(eventName, callback) {
if (typeof callback !== 'function') {
throw new TypeError('callback must be a function');
}
// 创建包装函数
const wrapper = (...args) => {
callback(...args);
// 执行后立即取消订阅
this.off(eventName, wrapper);
};
// 保存原始回调的引用,用于 off 方法
wrapper.origin = callback;
this.on(eventName, wrapper);
return this;
}
/**
* 清空指定事件的所有监听器
* @param {string} eventName - 事件名称(可选,不传则清空所有事件)
*/
clear(eventName) {
if (eventName) {
delete this.events[eventName];
} else {
this.events = {};
}
return this;
}
/**
* 获取指定事件的监听器数量
* @param {string} eventName - 事件名称
*/
listenerCount(eventName) {
return this.events[eventName] ? this.events[eventName].length : 0;
}
}
使用示例
// 创建事件管理器实例
const emitter = new EventEmitter();
// 示例1:基本的订阅和发布
function onLogin(username) {
console.log(`用户 ${username} 登录成功`);
}
emitter.on('login', onLogin);
emitter.emit('login', 'Alice'); // 输出: 用户 Alice 登录成功
// 示例2:多个监听器
emitter.on('login', (username) => {
console.log(`发送欢迎邮件给 ${username}`);
});
emitter.emit('login', 'Bob');
// 输出:
// 用户 Bob 登录成功
// 发送欢迎邮件给 Bob
// 示例3:取消订阅
emitter.off('login', onLogin);
emitter.emit('login', 'Charlie'); // 只输出: 发送欢迎邮件给 Charlie
// 示例4:单次订阅
emitter.once('payment', (amount) => {
console.log(`支付 ${amount} 元成功`);
});
emitter.emit('payment', 100); // 输出: 支付 100 元成功
emitter.emit('payment', 200); // 无输出(已自动取消订阅)
// 示例5:链式调用
emitter
.on('message', (msg) => console.log('收到消息:', msg))
.on('message', (msg) => console.log('消息长度:', msg.length))
.emit('message', 'Hello World');
// 输出:
// 收到消息: Hello World
// 消息长度: 11
// 示例6:清空事件
emitter.clear('message'); // 清空 message 事件的所有监听器
emitter.clear(); // 清空所有事件
// 示例7:获取监听器数量
emitter.on('test', () => {});
emitter.on('test', () => {});
console.log(emitter.listenerCount('test')); // 输出: 2
关键点
- 数据结构设计:使用对象存储事件名和监听器数组的映射关系,便于快速查找和管理
- 链式调用:所有方法返回
this,支持链式调用,提升代码可读性 - once 实现技巧:通过包装函数在执行后自动调用
off方法,同时保存原始回调引用以支持手动取消 - 参数传递:使用剩余参数(
...args)和展开运算符,支持任意数量的参数传递 - 边界处理:处理事件不存在、监听器为空等边界情况,增强代码健壮性
- 类型检查:对回调函数进行类型校验,避免运行时错误
- 灵活的 off 方法:支持移除指定回调或清空整个事件的所有监听器
- 实用工具方法:提供
listenerCount等辅助方法,方便调试和监控
目录