定义
EventBus是事件总线框架,可以用来简化组件之间通信和数据传输。它基于发布/订阅模式,实现业务代码解耦,提高开发效率。但也因为过于方便,所使用不慎,很有可能导致难以维护的问题。
构成
EventBus主要是有三个元素构成:
- 事件(Event)
- 发布者(Publisher)
- 订阅者(Subscriber)
上图是取自于github上EventBus。它是Android的事件总线框架,思想和实现大致都是一样,可以参考。
发布/订阅模式
定义了一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,是他们能够自动更新自己的状态。
思路
- 需要有个对象来存储事件,以事件名为key,回调为value,由于一个时间可能对应多个回调,所以存储回调的是个数组。
- 实现各个api
- on,订阅事件。将事件的回调存到events中。
- emit,触发事件。触发一次事件,会执行事件对应的所有回调。
- off,注销事件。清除事件对应的所有回调。
- once,订阅一个仅执行一次的事件。
实现
class EventEmitter {
constructor() {
this.events = {};
}
//触发,传递参数
emit(event, ...args) {
const cbs = this.events[event];
// 因为下方off会将this.events[event]重新赋值为null,所以需要判断一下
if (!cbs) {
console.log('没有当前事件');
return this;
}
//遍历执行所有回调
cbs.forEach((cb) => {
cb(...args)
});
// 为了可以链式调用
return this;
}
//监听,执行回调
on(event, cb) {
//如果events里面没有事件监听,那么就初始化为一个数组
//为什么是数组,因为一个事件可能有多个监听,你触发一次,多个监听都会执行
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(cb);
return this;
}
//移除监听回调
off(event, cb) {
//注册一个方法,只执行一次,执行完成后直接注销掉
const func = (...args) => {
//先把事件监听移除掉,再去执行cb
this.off(event, func);
cb(...args);
};
this.on(event, func);
return this;
}
// 只监听一次,执行回调
once(event, cb) {
//如果没有cb,那就意味着移除所有监听
//有的话,那就去除这个毁掉
if (!cb) {
this.events[event] = null;
} else {
this.events[event] = this.events[event].filter(
(it) => it !== cb
);
}
return this;
}
}
代码完成之后,测试一下
const event = new EventEmitter();
//累加求和
const add = (...args) => console.log(args.reduce((p, c) => p + c, 0));
//打印参数
const log = (...args) => console.log(...args);
event.on('add', add);
event.on('log', log);
event.emit('add', 1, 2); // 3
event.emit('log', 'hi'); // hi
event.off('add');
event.emit('add', 1, 2); // 打印为空,因为已经off掉了
event.once('onceAdd', add); //监听一次
event.emit('onceAdd', 1, 2, 3, 4); //10
event.emit('onceAdd', 1, 2, 3, 4, 5); //打印为空,
到这里,基本功能已经实现。
扩展
如果增加一个设置最大监听数的需求,该怎么实现呢?
-
在constructor中去定义最大监听数
constructor(maxListeners) {
this.events = {};
this.maxListeners = maxListeners || Infinity;
}
- 在on事件中实现逻辑,如果超出最大监听出,就直接返回。
//如果maxListeners不等于初始值,并且该事件的回调数量已经大于等于最大监听书,直接返回
if(this.maxListeners !== Infinity && this.events[event].length >= this.maxListeners) {
console.warn(`该事件${event}超过最大监听数`);
return this;
}