前端面试题:如何实现事件总线 Event Bus

介绍

通常作为多个模块间的通信机制,相当于一个事件管理中心,一个模块发送消息,其它模块接受消息,就达到了通信的作用。

原理

本质上是采用了发布-订阅的设计模式,比如多个模块 A、B、C 订阅了一个事件 EventX,然后某一个模块 X 在事件总线发布了这个事件,那么事件总线会负责通知所有订阅者 A、B、C,它们都能收到这个通知消息,同时还可以传递参数。

分析

如何使用 JavaScript 来实现一个简单版本的 Event Bus

  1. 创建一个类
  2. 又一个事件池,用来保存发布的事件
  3. 有一个发布的方法,将事件发布
  4. 有一个订阅的监听机制,来触发事件的回调
  5. 有取消订阅的机制
  6. 有只订阅一次的机制

实现

创建一个类

class EventBus {}

创建一个事件池

class EventBus {
  private events: {
    [key: string]: Array<{fn: Function, isOnce: boolean}>
  }

  constructor() {
    this.events = {}
  }
}

发布事件

  1. 第一个参数是事件的KEY值, 剩余是接收事件的参数
  2. 在处理事件池中的监听时,将只监听一次的事件去除
class EventBus {
  private events: {
    [key: string]: Array<{fn: Function, isOnce: boolean}>
  }

  constructor() {
    this.events = {}
  }
  emit(type: string, ...args: any[]) {
    const fnList = this.events[type]
    if (fnList == null) return
    this.events[type] = fnList.filter(item => {
      const {fn, isOnce} = item
      fn(...args)
      return !isOnce
    })
  }
}

订阅事件

class EventBus {
  private events: {
    [key: string]: Array<{fn: Function, isOnce: boolean}>
  }

  constructor() {
    this.events = {}
  }
  on(type: string, fn: Function, isOnce:boolean=false){
    const events = this.events
    if (events[type] == null) {
      this.events[type] = []
    }
    this.events[type].push({fn: fn, isOnce})
  }
  emit(type: string, ...args: any[]) {
    const fnList = this.events[type]
    if (fnList == null) return
    this.events[type] = fnList.filter(item => {
      const {fn, isOnce} = item
      fn(...args)
      return !isOnce
    })
  }
}

只订阅一次

class EventBus {
  private events: {
    [key: string]: Array<{fn: Function, isOnce: boolean}>
  }

  constructor() {
    this.events = {}
  }
  on(type: string, fn: Function, isOnce:boolean=false){
    const events = this.events
    if (events[type] == null) {
      this.events[type] = []
    }
    this.events[type].push({fn: fn, isOnce})
  }
  once(type: string, fn: Function) {
    this.on(type, fn, true)
  }
  emit(type: string, ...args: any[]) {
    const fnList = this.events[type]
    if (fnList == null) return
    this.events[type] = fnList.filter(item => {
      const {fn, isOnce} = item
      fn(...args)
      return !isOnce
    })
  }
}

取消订阅

class EventBus {
  private events: {
    [key: string]: Array<{fn: Function, isOnce: boolean}>
  }

  constructor() {
    this.events = {}
  }
  on(type: string, fn: Function, isOnce:boolean=false){
    const events = this.events
    if (events[type] == null) {
      this.events[type] = []
    }
    this.events[type].push({fn: fn, isOnce})
  }
  once(type: string, fn: Function) {
    this.on(type, fn, true)
  }
  emit(type: string, ...args: any[]) {
    const fnList = this.events[type]
    if (fnList == null) return
    this.events[type] = fnList.filter(item => {
      const {fn, isOnce} = item
      fn(...args)
      return !isOnce
    })
  }
  off(type: string, fn: Function) {
    this.events[type] = this.events[type].filter(item => {
      return item.fn !== fn
    })
  }
}

取消订阅某个事件

class EventBus {
  private events: {
    [key: string]: Array<{fn: Function, isOnce: boolean}>
  }

  constructor() {
    this.events = {}
  }
  on(type: string, fn: Function, isOnce:boolean=false){
    const events = this.events
    if (events[type] == null) {
      this.events[type] = []
    }
    this.events[type].push({fn: fn, isOnce})
  }
  once(type: string, fn: Function) {
    this.on(type, fn, true)
  }
  emit(type: string, ...args: any[]) {
    const fnList = this.events[type]
    if (fnList == null) return
    this.events[type] = fnList.filter(item => {
      const {fn, isOnce} = item
      fn(...args)
      return !isOnce
    })
  }
  off(type: string, fn: Function) {
    // 如果没传 函数就是解除所有
    if (!fn) {
      this.events[type] = []
    }else {
      this.events[type] = this.events[type].filter(item => {
        return item.fn !== fn
      })
    }
  }
}

单例模式应用

在上层实例中单例

将事件总线引入到上层实例使用,只需要保证在一个上层实例中只有一个 EventBus,如果上层实例有多个,意味着有多个事件总线,但是每个上层实例管控自己的事件总线。
首先在上层实例中建立一个变量用来存储事件总线,只在第一次使用时初始化,后续其他模块使用事件总线时直接取得这个事件总线实例。

// 上层实例
class LWebApp {
  private _eventBus?: EventBus;

  constructor() {}

  public getEventBus() {
    // 第一次初始化
    if (this._eventBus == undefined) {
      this._eventBus = new EventBus();
    }

    // 后续每次直接取唯一一个实例,保持在LWebApp实例中单例
    return this._eventBus;
  }
}

// 使用
const eventBus = new LWebApp().getEventBus();

在全局中单例

有时候我们希望不管哪一个模块想使用我们的事件总线,我们都想这些模块使用的是同一个实例,这就是全局单例,这种设计能更容易统一管理事件。
写法同上面的类似,区别是要把 _eventBus 和 getEventBus 转为静态属性。使用时无需实例化 EventBusTool 工具类,直接使用静态方法就行了。

// 上层实例
class EventBusTool {
  private static _eventBus?: EventBus;

  constructor() {}

  public static getEventBus(): EventBus {
    // 第一次初始化
    if (this._eventBus == undefined) {
      this._eventBus = new EventBus();
    }

    // 后续每次直接取唯一一个实例,保持全局单例
    return this._eventBus;
  }
}

// 使用
const eventBus = EventBusTool.getEventBus();

2周刷完100道前端优质面试真题,双越老师力作

链接:https://pan.baidu.com/s/1Fr4TUipNKGyaZxssKIkc3w
提取码:ylsp

也欢迎使用我的小程序,惊喜不断!

xcxm.jpg

posted on 2023-02-13 16:52  不锈钢子  阅读(269)  评论(0编辑  收藏  举报