发布订阅模式在前端开发中非常常见,用于解耦组件间的通信。
适用于各种前端场景,如组件通信、状态同步、跨层级数据传递等。
基础实现
class EventEmitter {
constructor() {
this.events = {};
}
// 订阅事件
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
return this; // 支持链式调用
}
// 发布事件
emit(eventName, ...args) {
const callbacks = this.events[eventName];
if (!callbacks || callbacks.length === 0) {
return false;
}
callbacks.forEach(callback => {
try {
callback.apply(this, args);
} catch (error) {
console.error(`Error executing callback for event "${eventName}":`, error);
}
});
return true;
}
// 取消订阅
off(eventName, callback) {
const callbacks = this.events[eventName];
if (!callbacks) {
return this;
}
if (!callback) {
// 如果没有提供具体回调,则移除该事件的所有监听器
delete this.events[eventName];
} else {
// 移除指定的回调
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
// 如果没有监听器了,删除该事件
if (callbacks.length === 0) {
delete this.events[eventName];
}
}
return this;
}
// 一次性订阅
once(eventName, callback) {
const onceWrapper = (...args) => {
callback.apply(this, args);
this.off(eventName, onceWrapper);
};
return this.on(eventName, onceWrapper);
}
}
使用
// 创建事件中心
const eventBus = new EventEmitter();
// 订阅事件
eventBus.on('user-login', (userData) => {
console.log('用户登录:', userData);
});
eventBus.on('user-login', (userData) => {
console.log('更新用户界面:', userData.name);
});
// 发布事件
eventBus.emit('user-login', {
id: 1,
name: '张三',
email: 'zhangsan@example.com'
});
// 输出:
// 用户登录: {id: 1, name: "张三", email: "zhangsan@example.com"}
// 更新用户界面: 张三
// 一次性订阅
eventBus.once('notification', (message) => {
console.log('一次性通知:', message);
});
eventBus.emit('notification', '第一次触发'); // 会执行
eventBus.emit('notification', '第二次触发'); // 不会执行
// 取消订阅
const handleClick = (data) => console.log('点击:', data);
eventBus.on('button-click', handleClick);
eventBus.emit('button-click', '按钮1'); // 会执行
eventBus.off('button-click', handleClick);
eventBus.emit('button-click', '按钮2'); // 不会执行
TS 版
interface EventMap {
[key: string]: (...args: any[]) => void;
}
class TypedEventEmitter<T extends EventMap> {
private events: Partial<Record<keyof T, Function[]>> = {};
on<K extends keyof T>(eventName: K, callback: T[K]) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName]!.push(callback);
return this;
}
emit<K extends keyof T>(eventName: K, ...args: Parameters<T[K]>) {
const callbacks = this.events[eventName];
if (!callbacks) return false;
callbacks.forEach(callback => callback(...args));
return true;
}
}
// 使用
interface MyEvents {
'user-login': (user: { id: number; name: string }) => void;
'data-loaded': (data: any[], timestamp: number) => void;
}
const emitter = new TypedEventEmitter<MyEvents>();
emitter.on('user-login', (user) => {
console.log(user.name); // 类型安全
});
emitter.emit('user-login', { id: 1, name: '张三' });
注意事项
内存管理:及时移除不再需要的监听器,避免内存泄漏
错误处理:监听器中的错误应该被捕获,避免影响其他监听器
性能考虑:对于高频事件,考虑使用防抖或节流
调试友好:可以添加事件日志功能,便于调试
单例模式:通常全局使用一个事件总线实例即可
评论区