Skip to content

手撕事件总线

基础版本

先写一个最基础的版本,可以实现事件的监听和触发,其中对于触发,我们需要考虑传参问题。

event-bus-base.js
js
class EventBus {
  constructor() {
    this.eventObj = {};
  }

  on(eventName, callback) {
    if (!this.eventObj[eventName]) {
      this.eventObj[eventName] = [];
    }
    this.eventObj[eventName].push(callback);
  }

  emit(eventName, ...args) {
    if (this.eventObj[eventName]) {
      this.eventObj[eventName].forEach((callback) => {
        callback(...args);
      });
    }
  }
}

const eventBus = new EventBus();
eventBus.on("click", (data) => {
  console.log("click", data);
});
eventBus.emit("click", "data1"); // click data1
eventBus.emit("click", "data2"); // click data2

多实例

接下来,我们考虑多实例的问题,这里就涉及到了事件的取消监听,并且这里很大一部分都要进行改动——因为对于每个实例都要有唯一标识。

event-bus-multi.js
js
class EventBus {
  constructor() {
    this.eventObj = {};
    this.callbackId = 0;
  }
  on(eventName, callback) {
    if (!this.eventObj[eventName]) {
      this.eventObj[eventName] = {};
    }
    const id = this.callbackId++;
    this.eventObj[eventName][id] = callback;
    return id;
  }

  emit(eventName, ...args) {
    if (this.eventObj[eventName]) {
      const callbacks = this.eventObj[eventName];
      for (const id in callbacks) {
        callbacks[id](...args);
      }
    }
  }

  off(eventName, id) {
    if (this.eventObj[eventName] && this.eventObj[eventName][id]) {
      delete this.eventObj[eventName][id];
    }
    console.info(`off ${eventName} ${id}`);
    if (!Object.keys(this.eventObj[eventName]).length) {
      delete this.eventObj[eventName];
    }
  }
}

const eventBus = new EventBus();
const id1 = eventBus.on("click", (data) => {
  console.log("click-id1", data);
});
const id2 = eventBus.on("click", (data) => {
  console.log("click-id2", data);
});

eventBus.emit("click", "hello");
// click-id1 hello
// click-id2 hello
eventBus.off("click", id1);
eventBus.emit("click", "hello");
// off click 0
// click-id2 hello

销毁以及只执行一次

最后,再考虑一下事件总线的销毁和只执行一次的事件。

event-bus-destroy.js
js
class EventBus {
  // ...

  emit(eventName, ...args) {
    if (this.eventObj[eventName]) {
      const callbacks = this.eventObj[eventName];
      for (const id in callbacks) {
        callbacks[id](...args);
        if (id.indexOf("D" !== -1)) {
          delete this.eventObj[eventName][id];
        }
      }
    }
  }

  // ...

  once(eventName, callback) {
    if (!this.eventObj[eventName]) {
      this.eventObj[eventName] = {};
    }
    const id = "D" + this.callbcakId++;
    this.eventObj[eventName][id] = callback;
    return id;
  }

  del(eventName) {
    if (this.eventObj[eventName]) {
      delete this.eventObj[eventName];
    }
  }

  clear() {
    this.eventObj = {};
  }
}

总结

呈上最终完整的代码:

event-bus.js
js
class EventBus {
  constructor() {
    this.eventObj = {};
    this.callbackId = 0;
  }
  on(eventName, callback) {
    if (!this.eventObj[eventName]) {
      this.eventObj[eventName] = {};
    }
    const id = this.callbackId++;
    this.eventObj[eventName][id] = callback;
    return id;
  }

  emit(eventName, ...args) {
    if (this.eventObj[eventName]) {
      const callbacks = this.eventObj[eventName];
      for (const id in callbacks) {
        callbacks[id](...args);
        if (id.indexOf("D") !== -1) {
          delete this.eventObj[eventName][id];
        }
      }
    }
  }

  off(eventName, id) {
    if (this.eventObj[eventName] && this.eventObj[eventName][id]) {
      delete this.eventObj[eventName][id];
    }
    console.info(`off ${eventName} ${id}`);
    if (!Object.keys(this.eventObj[eventName]).length) {
      delete this.eventObj[eventName];
    }
  }

  once(eventName, callback) {
    if (!this.eventObj[eventName]) {
      this.eventObj[eventName] = {};
    }
    const id = "D" + this.callbackId++;
    this.eventObj[eventName][id] = callback;
    return id;
  }

  del(eventName) {
    if (this.eventObj[eventName]) {
      delete this.eventObj[eventName];
    }
  }

  clear() {
    this.eventObj = {};
  }

  getEvents() {
    console.log(this.eventObj);
  }
}

const bus = new EventBus();
const id1 = bus.on("click", (data) => {
  console.log("click-id1", data);
});
const id2 = bus.on("click", (data) => {
  console.log("click-id2", data);
});
const id3 = bus.once("double-click", (data) => {
  console.log("double-click-id3", data);
});
bus.getEvents();
// {
//     click: { '0': [Function (anonymous)], '1': [Function (anonymous)] },
//     'double-click': { D2: [Function (anonymous)] }
// }
bus.emit("click", "data1");
// click-id1 data1
// click-id2 data1
bus.emit("double-click", "data2");
// double-click-id3 data2
bus.getEvents();
// {
//     click: { '0': [Function (anonymous)], '1': [Function (anonymous)] },
//     'double-click': {}
// }
bus.off("click", id1);
bus.getEvents();
// { click: { '1': [Function (anonymous)] }, 'double-click': {} }
bus.del("click");
bus.getEvents();
// { 'double-click': {} }
bus.clear();
bus.getEvents();
// {}