在组件的通信中 EventBus 非常经典,你能手写实现下 EventBus 么?

721 阅读2分钟

首先,清楚自己要做什么

<div id="btn">点击这里触发消息</div>

btn.onclick = function(){
  EventBus.emit('sendMsg',this.innerText);
}

EventBus.on('sendMsg',(msg)=>{
  console.log(`订阅的消息是:${msg}`);
});

首先,看上面的代码,按钮再点击的时候,我们通过 EventBus 去发送一个消息,然后下面的 EventBus.on方法里面能监听到事件被触发,并且还需要传递一些参数过来,这个就是 EventBus 的核心逻辑,其他的功能都是处理边缘情况,稍后再说

那么如果我们要实现这样的功能,要怎么写呢? 直接上答案:

class EventBusClass{
    constructor() {
        this.msgList = {};
    }
    on(msgName,fn){
        this.msgList[msgName] = fn;
    }
    emit(msgName,msg){
        if(!this.msgList.hasOwnProperty(msgName)){
            return
        }
				this.msgList[msgName](msg);
    }
}

const eventBus = new EventBusClass();
window.EventBus = eventBus;

其实核心逻辑就这么几行代码,实现一个类,首先在 constructor 里找一个地方去存消息列队,直接用一个对象来存就好了

在 on 方法里去注册事件,其实就是把传进来的函数保存到消息列队里去了而已

然后呢,在 emit 方法里去调用,如果有,直接把消息列队里的方法拿出来,执行,并且传入参数,那如果没有呢? 当然就直接略过咯

然后我们再来处理一些边缘情况,首先 on 方法里面,一个消息名称里面可能会保存有多个函数,也就是说这样去写应该也是OK的,所以:

btn.onclick = function(){
  EventBus.emit('sendMsg',this.innerText);
}

EventBus.on('sendMsg',(msg)=>{
  console.log(`订阅的消息是:${msg}`);
});
EventBus.on('sendMsg',(msg)=>{
  console.log(`订阅的消息2是:${msg}`);
});

可以看到在底下用 on 方法注册了两件事情,这两件事情都要触发,那么这时候咱就不能简单粗暴的去保存消息了,而是要把消息放在数组里,但是又要考虑一点性能,所以并不是所有的消息都是数组,这时候就需要我们在注册的时候进行一些判断:

on(msgName,fn){
  if(this.msgList.hasOwnProperty(msgName)){//首先判断消息名称是不是已经保存过了
    if(typeof this.msgList[msgName] === 'function'){//如果已经存在,看看是不是一个函数
      this.msgList[msgName] = [this.msgList[msgName],fn]//如果是函数,则需要变成数组
    }else {
      this.msgList[msgName] = [...this.msgList[msgName], fn]//如果是数组,则需要在原有的数组上把新的函数保存进去,这里用push方法其实也是OK的
    }
  }else{
    this.msgList[msgName] = fn;//如果消息名称在事件队列里面不存在,那么直接存就可以了,不需要一上来就存数组
  }
}

首先判断消息名称是不是已经保存过了,如果存在的话说明这个消息名称里面已经有函数或者已经有多个函数是一个数组了,这时候就需要判断是不是函数,如果是函数,变成数组,如果是数组,则直接把新的函数往数组里面扔就可以了

如果消息名称不存在,那么直接简单粗暴保存就可以了,不需要一上来就保存数组

既然注册的方法改了,那么相应的 emit 方法也需要进行修改

emit(msgName,msg){
  if(!this.msgList.hasOwnProperty(msgName)){//还是先判断消息名称是否存在
    return
  }

  if(typeof this.msgList[msgName] === 'function'){//再看看消息是函数还是数组
    this.msgList[msgName](msg);//如果是函数,直接执行就好了
  }else{
    this.msgList[msgName].map((fn)=>{//如果是数组,则需要把数组取出来挨个执行
      fn(msg);
    })
  }
}

和之前一样还是先判断消息名称是不是存在的,如果存在,再往下走

存在的话消息名称下保存的可能是一个函数,也可能是一个数组,这时候再区分对待一下就好了

最后再简单实现一个移除方法就差不多了,所有代码:

class EventBusClass{
    constructor() {
        this.msgList = {};
    }
    on(msgName,fn){
        if(this.msgList.hasOwnProperty(msgName)){
            if(typeof this.msgList[msgName] === 'function'){
                this.msgList[msgName] = [this.msgList[msgName],fn]
            }else {
                this.msgList[msgName] = [...this.msgList[msgName], fn]
            }
        }else{
            this.msgList[msgName] = fn;
        }
    }
    one(msgName,fn){
        this.msgList[msgName] = fn;
    }
    emit(msgName,msg){
        if(!this.msgList.hasOwnProperty(msgName)){
            return
        }

        if(typeof this.msgList[msgName] === 'function'){
            this.msgList[msgName](msg);
        }else{
            this.msgList[msgName].map((fn)=>{
                fn(msg);
            })
        }
    }
    off(msgName){
        if(!this.msgList.hasOwnProperty(msgName)){
            return;
        }
        delete this.msgList[msgName];
    }
}

const eventBus = new EventBusClass();
window.EventBus = eventBus;