使用JavaScript带你一步步手写简单的事件总线

198 阅读3分钟

实际开发中使用已经写好的就行,这里是为了让你理解事件总线核心是怎么实现的

实现

    // 类EventBus -> 事件总线对象
    class HYEventBus {
      constructor() {
        // 存储事件名和事件处理函数的对应关系
        this.eventMap = {}
      }
      
      on(eventName, eventFn) {

        let eventFns = this.eventMap[eventName]

        if (!eventFns) {
          eventFns = []

          this.eventMap[eventName] = eventFns
        }

        eventFns.push(eventFn)
      }
      
      off(eventName, eventFn) {
        let eventFns = this.eventMap[eventName]
        if (!eventFns) return
        for (let i = 0; i < eventFns.length; i++) {
          const fn = eventFns[i]
          if (fn === eventFn) {
            eventFns.splice(i, 1)
            break
          }
        }

        // 如果eventFns已经清空了
        if (eventFns.length === 0) {
          delete this.eventMap[eventName]
        }
      }

      emit(eventName, ...args) {
        let eventFns = this.eventMap[eventName]
        if (!eventFns) return
        eventFns.forEach(fn => {
          fn(...args)
        })
      }
    }

代码解释

class HYEventBus {
    constructor() {
        // 存储事件名和事件处理函数的对应关系
        this.eventMap = {}
    }
}
  • 这里定义了一个名为 HYEventBus 的类,通过构造函数初始化了一个空对象 this.eventMap,它的作用是用来存储不同事件名对应的事件处理函数数组,以事件名为键,事件处理函数数组为值形成一种映射关系

2. on 方法

on(eventName, eventFn) {
    let eventFns = this.eventMap[eventName];
    if (!eventFns) {
        eventFns = [];
        this.eventMap[eventName] = eventFns;
    }
    eventFns.push(eventFn);
}

用于订阅事件。接收一个事件名 eventName 和一个对应的事件处理函数 eventFn。先尝试从 this.eventMap 中获取该事件名对应的处理函数数组

如果不存在(也就是首次为该事件名添加订阅函数),就创建一个空数组来存放后续的处理函数。然后将传入的事件处理函数 eventFn 推送到对应的数组中,这样同一个事件名可以有多个订阅者,它们的处理函数都会被存放在相应的数组里。

3. off 方法

off(eventName, eventFn) {
    let eventFns = this.eventMap[eventName];
    if (!eventFns) return;
    for (let i = 0; i < eventFns.length; i++) {
        const fn = eventFns[i];
        if (fn === eventFn) {
            eventFns.splice(i, 1);
            break;
        }
    }

    // 如果eventFns已经清空了
    if (eventFns.length === 0) {
        delete this.eventMap[eventName];
    }
}

用于取消订阅事件。它接收事件名 eventName 和要取消的具体事件处理函数 eventFn

  • 首先查找对应事件名的处理函数数组,如果存在该数组,就遍历数组去找到与传入的 eventFn 相等的函数,找到后通过 splice 方法将其从数组中移除。

  • 如果移除后该事件对应的处理函数数组为空了,说明没有任何函数再订阅这个事件了,那就从 this.eventMap 中将这个事件名对应的键值对整个删除掉,以清理不再使用的记录。

4. emit 方法

emit(eventName,...args) {
    let eventFns = this.eventMap[eventName];
    if (!eventFns) return;
    eventFns.forEach(fn => {
        fn(...args);
    });
}

用于触发某个事件。接收一个事件名 eventName 以及不定数量的参数(通过剩余参数语法 ...args 收集),

先查找该事件名对应的处理函数数组,如果存在,就遍历这个数组,依次调用每个处理函数,并将收集到的参数传递给这些处理函数,从而实现通知所有订阅该事件的部分去执行相应的逻辑。

测试

    // 使用过程
    const eventBus = new HYEventBus()

    // 添加订阅操作
    eventBus.on("navclick", (name, age, height) => {
      console.log("navclick listener 01", name, age, height)
    })

    const click =  () => {
      console.log("navclick listener 02")
    }
    eventBus.on("navclick", click)

    // 取消订阅操作
    setTimeout(() => {
      eventBus.off("navclick", click)
    }, 5000);

  
    const navBtnEl = document.querySelector(".nav-btn")
    navBtnEl.onclick = function() {
      console.log("自己监听到")
      // 发布订阅操作
      eventBus.emit("navclick", "why", 18, 1.88)
    }

image.png