文章

基于发布订阅模式实现事件总线

Interview

手写基于发布订阅模式实现事件总线

基于发布订阅模式实现事件总线

题目

基于发布订阅模式实现一个事件总线

解题思路

观察题目,“发布订阅模式”、“事件总线”等字眼突出

什么是发布订阅模式?

发布/订阅Publish–subscribe pattern)是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。——来自维基百科

我的理解是:不管是发布者还是订阅者都处于一个事件容器中,通过映射来表示发布者与订阅者之间的关系。一个消息可能需要通知很多订阅者,故使用数组来装载不同的订阅者。简而言之,就是发布者作为 key,订阅者作为 value,且它是一个数组。

什么是事件总线?

事件总线是对发布-订阅模式的一种实现(所以看起来这个问题似乎在说同一种东西),事件总线在八股文中常说到:它是一种跨组件通信的方法,适用于项目小的情况。缺点也很明显,在 Vue 中使用事件总线,容易出现重复触发的情况。并且在项目变大时,事件总线的管理难度也会变大。

在事件总线中,on 事件表示监听事件,off 事件表示销毁事件,emit 事件表示触发事件。那么,其实一一对应着原来的 addEventListener、removeEventListener、dispatchEvent。

实现

class EventBus {
  constructor() {
    // 事件容器
    this.events = {};
  }
 
  // 添加事件(订阅事件)
  on(event, callback) {
    if (!this.events[event]) {
      // 创建个新容器
      this.events[event] = [];
    }
    // 将事件存入
    this.events[event].push(callback);
  }
 
  // 触发事件(发布事件)
  emit(event, ...params) {
    if (!this.events[event]) {
      throw new Error("未注册该事件");
    }
 
    this.events[event].forEach((callback) => callback(...params));
  }
 
  // 删除事件(如果没有第二个参数,则将发布和订阅全部清除)
  off(event, callback) {
    if (!this.events[event]) {
      throw new Error("未注册该事件");
    }
 
    if (!callback) {
      delete this.events[event];
    } else {
      const index = this.events[event].forEach((item) => item === callback);
 
      if (index === -1) {
        throw new Error("未找到该订阅事件");
      }
 
      this.events[event].splice(index, 1);
 
      if (this.events[event].length === 0) {
        delete this.events[event];
      }
    }
  }
}