手写一个自己的EventEmitter🤔

202 阅读2分钟

什么是事件总线、EventBus、EventEmitter ?

事件总线是一种设计模式,可以让不同的部件之间松散耦合地通信。一个事件总线通常由事件生产者和事件消费者组成。事件生产者会在事件总线上发布消息,而事件消费者则订阅这些消息,并在收到消息时执行相应的操作。

事件总线有很多方法, 这里我们只需要明白其中最核心的三个方法原理即可

第一个是 on 方法 监听一个事件

on(eventName, eventFn, thisArg) {

// 事件池中没有该事件 则创建一个 有则直接push 进去

this.eventMap[eventName] = this.eventMap[eventName] || [];

this.eventMap[eventName].push({

eventFn,

thisArg,

});

}

第二个是 off 方法, 取消事件监听

off(eventName, eventFn) {

if (!this.eventMap[eventName]) return;

// const newEventMap = [...this.eventMap[eventName]]; // 为了不影响原来的数组

// for (let i = 0; i < newEventMap.length; i++) {

// const fn = newEventMap[i];

// if (fn.eventFn === eventFn) {

// const index = this.eventMap[eventName].indexOf(fn);

// this.eventMap[eventName].splice(index, 1);

// }

// }

  

// 优化 filter不会改变原数组 会返回一个新数组

this.eventMap[eventName] = this.eventMap[eventName].filter(

(fn) => fn.eventFn !== eventFn

);

}

第三个方法, 发射一个事件 emit

emit(eventName, ...payload) {

if (!this.eventMap[eventName]) return;

this.eventMap[eventName].forEach((fn) =>

fn.eventFn.apply(fn.thisArg, payload)

);

}

完整代码

class MyEventBus {

constructor() {

// 事件池

this.eventMap = {}; // {abc: [fn1, fn2]}

}

on(eventName, eventFn, thisArg) {

// 事件池中没有该事件 则创建一个 有则直接push 进去

this.eventMap[eventName] = this.eventMap[eventName] || [];

this.eventMap[eventName].push({

eventFn,

thisArg,

});

}

off(eventName, eventFn) {

if (!this.eventMap[eventName]) return;

// const newEventMap = [...this.eventMap[eventName]]; // 为了不影响原来的数组

// for (let i = 0; i < newEventMap.length; i++) {

// const fn = newEventMap[i];

// if (fn.eventFn === eventFn) {

// const index = this.eventMap[eventName].indexOf(fn);

// this.eventMap[eventName].splice(index, 1);

// }

// }

  

// 优化 filter不会改变原数组 会返回一个新数组

this.eventMap[eventName] = this.eventMap[eventName].filter(

(fn) => fn.eventFn !== eventFn

);
if (this.eventMap[eventName].length === 0) {

delete this.eventMap[eventName];

}

}

emit(eventName, ...payload) {

if (!this.eventMap[eventName]) return;

this.eventMap[eventName].forEach((fn) =>

fn.eventFn.apply(fn.thisArg, payload)

);

}

}
使用方法
const eventZ = new MyEventBus();

  

const abc1 = function () {

console.log("监听abc1", this);

};

const abc2 = function () {

console.log("监听abc2", this);

};

  

const aliyu = {

name: "小鱼儿",

age: 18,

};

  

//使用

eventZ.on("abc", abc1, aliyu);

eventZ.on("abc", abc2, { name: "张三" });

  

function publish() {

eventZ.emit("abc");

}

  

document.querySelector("button").addEventListener("click", publish);

setTimeout(() => {

eventZ.off("abc", abc1);

eventZ.off("abc", abc2);

console.log("取消监听");

}, 3000);
EventEmitter 的缺点
  • 如果一个事件有多个监听器,那么这些监听器会按照它们被添加的顺序依次执行。这可能导致一些问题,因为监听器可能会依赖于其他监听器的执行结果。
  • 如果一个监听器抛出异常,则会导致整个事件处理过程被中断,剩余的监听器也不会再执行。
  • EventEmitter 可能会出现内存泄漏的问题,如果一个对象持有了 EventEmitter,并且注册了很多监听器,但是却没有及时解除监听,那么这些监听器就可能会一直存在,导致内存泄漏。
  • EventEmitter 的设计偏向于观察者模式,并不适用于所有场景。比如,在某些情况下,你可能希望消息能够被广播给所有监听器,而不是在第一个监听器中断事件处理过程之后就停止。其实是可以有方法实现的,本人现阶段能力有限,如果有大佬实现,麻烦贴链接学习下,感谢感谢

尽管 EventEmitter 存在这些缺点,但是它仍然是一个很有用的工具,可以在适当的场景下使用。