前端-手写简易EventBus

4,241 阅读3分钟

定义

EventBus是事件总线框架,可以用来简化组件之间通信和数据传输。它基于发布/订阅模式,实现业务代码解耦,提高开发效率。但也因为过于方便,所使用不慎,很有可能导致难以维护的问题。

构成

EventBus主要是有三个元素构成:

  • 事件(Event)
  • 发布者(Publisher)
  • 订阅者(Subscriber)

eventbus.png

上图是取自于github上EventBus。它是Android的事件总线框架,思想和实现大致都是一样,可以参考。

发布/订阅模式

定义了一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,是他们能够自动更新自己的状态。

思路

  1. 需要有个对象来存储事件,以事件名为key,回调为value,由于一个时间可能对应多个回调,所以存储回调的是个数组。
  2. 实现各个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); //打印为空,

到这里,基本功能已经实现。

扩展

如果增加一个设置最大监听数的需求,该怎么实现呢?

  1. 在constructor中去定义最大监听数

    constructor(maxListeners) {
        this.events = {};
        this.maxListeners = maxListeners || Infinity;
    }
  1. 在on事件中实现逻辑,如果超出最大监听出,就直接返回。
//如果maxListeners不等于初始值,并且该事件的回调数量已经大于等于最大监听书,直接返回
    if(this.maxListeners !== Infinity && this.events[event].length >= this.maxListeners) {
        console.warn(`该事件${event}超过最大监听数`);
        return this;
    }
				

参考

前端面试(蚂蚁金服笔试) - 手写事件总线 EventBus