手写实现一个浏览器端的EventEmitter
「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。
EventEmitter是什么
javascript这门语言,除了前端我们常用的DOM(描述处理网页内容的方法和接口)、BOM(描述与浏览器进行交互的方法和接口)等,还有用于后端的Node.js,其中内置的各种模块里,events就是其中比较重要的一个模块,因为Node.js的事件驱动机制就是建立在events模块上,许多需要实现异步事件驱动架构Node.js模块都内置了events,掌握其实现原理还是非常重要的。 events模块对外提供了一个EventEmitter对象,用于对Node中的事件进行统一管理。EventEmitter对象上的一些重要方法和功能如下。
- addListener(event,listener),给指定事件添加一个监听器,on方法的别名。
- emit(event,[arg1],[arg2],[arg3]...),根据参数的顺序执行每个监听器。
- removeListener(event,listener),移除指定事件的某个监听器,off方法的别名。
- removeAllListener([event]),移除指定事件的所有监听器,如果没有指定事件,则是移除所有事件的监听器。
- once(event,listener),为指定事件注册一个一次性的监听器,监听器触发一次后立即移除该监听器。
- listeners(event),返回指定事件的监听器数组。
EventEmitter的用法
介绍了这么久,我们赶紧看看EventEmitter该如何使用,其实很简单,代码如下。
// 定义三个监听器函数
function hello_1(name){
console.log('hello',name);
}
function hello_2(name){
console.log('HELLO',name);
}
function hello_3(name){
console.log('hello HELLO',name);
}
const events = require('events');
let eventEmitter = new events.EventEmitter();
eventEmitter.addListener('hello',hello_1);
eventEmitter.addListener('hello',hello_2);
eventEmitter.once('hello',hello_3);
eventEmitter.emit('hello','xiaomi');
// 输出 hello xiaomi
// 输出 HELLO xiaomi
// 输出 hello HELLO xiaomi
eventEmitter.removeListener('hello',hello_1); // 移除hello事件对应的hello_1监听器
eventEmitter.emit('hello','xiaomi');
// 只输出 HELLO xiaomi ,因为once,所以hello_3触发一次后就被移除了
eventEmitter.removeAllListener('hello'); // 移除hello事件对应的所有监听器
eventEmitter.emit('hello','xiaomi');
// 没有任何输出,说明移除了hello事件的所有监听器
通过new实例化一个EventEmitter对象,然后通过实例上的addListener方法监听hello事件并定义一个对应的执行函数,用once定义一个只触发一次的监听器,再通过emit方法派发hello事件通知执行函数执行,最后移除hello事件的监听器。这就是一般使用方式,用到了addListener、once、emit、removeListener、removeAllListener等方法,这里补充一个知识点,添加一个事件监听,在页面销毁的时候就应该移除这个事件监听,以免造成不必要的内存浪费,比如上面的addListener 和 removeListener,这类似的还有比如on 和 off、subscribe 和 unsubscribe、setInterval 和 clearInterval等。 看完使用方式,大家一起思考下,EventEmitter应用了什么设计模式?以及这样的方式组织代码有什么益处? 答案我们在最后再补上。
手写实现一个EventEmitter
结合上面的介绍,我们先来实现一个浏览器端的基础版EventEmitter,其中包括addListener、once、emit、removeListener、removeAllListener这些方法。
class EventEmitter {
constructor(){
// 初始化__events对象,用于存放自定义事件和对应的回调函数
/*
对象结构:{
'hello':[{listener: function,once: boolean},...]
}
*/
this.__events = {};
}
addListener(event,listener){
if (!event || !listener) return;
if (!isValidListener(listener)) {
throw new TypeError('listener must be a function');
}
let events = this.__events;
let listeners = (events[event] = events[event] || []);
// 用于判断是否是对象,如果是函数则包裹成目标对象
let listenerIsWrapped = typeof listener === 'object';
// 排重,相同的监听器只需要添加一次
if (indexOfListener(listeners, listener) === -1) {
listeners.push(
listenerIsWrapped ? listener : {
listener: listener,
// once 属性用于判断处理once机制
once: false
}
);
}
return this;
}
removeListener(event,listener){
let listeners = this.__events[event];
if (!listeners) return;
let index;
for (let i = 0, len = listeners.length; i < len; i++) {
if (listeners[i] && listeners[i].listener === listener) {
index = i;
break;
}
}
// 删除__events中对应的监听器
if (typeof index !== 'undefined') {
listeners.splice(index, 1);
}
return this;
}
removeAllListener(event){
// 如果该 event 存在,则将其对应的 listeners 的数组直接清空
if (event && this.__events[event]) {
this.__events[event] = [];
} else { // 全部清空
this.__events = {};
}
}
once(event,listener){
// 直接调用 addListener 方法,once 参数传入 true,待执行之后进行 once 处理
return this.addListener(event, {
listener: listener,
once: true
});
}
emit(event,...args){
// 直接通过内部对象获取对应自定义事件的回调函数数组
let listeners = this.__events[event];
if (!listeners) return;
// 需要考虑多个 listener 的情况
listeners.forEach(listener => {
listener.listener.apply(this, args || []);
// 给 listener 中 once 为 true 的进行特殊处理
if (listener.once) {
this.removeListener(event, listener.listener);
}
});
return this
}
}
// 判断是否是合法的 listener
function isValidListener(listener) {
if (typeof listener === 'function') {
return true;
} else if (listener && typeof listener === 'object') {
return isValidListener(listener.listener);
} else {
return false;
}
}
// 查找新增自定义事件在__events里的位置,-1则说明不存在
function indexOfListener(array, item) {
let result = -1;
item = typeof item === 'object' ? item.listener : item;
for (let i = 0, len = array.length; i < len; i++) {
if (array[i].listener === item) {
result = i;
break;
}
}
return result
}
从代码中可以看出addListener的实现思路就是当调用订阅一个自定义事件的时候,只要该事件校验成功后,就把该自定义事件 push到this.__events这个对象中存储,等emit发布的时候,则直接获取__events中对应事件的listener回调函数,而后直接执行该回调函数就能实现想要的效果。emit方法其实就是通过__events拿出对应自定义事件的回调函数数组遍历执行,在执行过程中对once选项为true的情况,额外触发removeListener方法移除该自定义事件的该回调函数,从而实现自定义事件只执行一次的效果。once方法也就是执行一次addListener方法,不过增加了个once为true的标志,在emit时做额外的处理。removeListener和removeAllListener也很简单,就是根据__events对象完成删除或者清空的操作。
测试结果
如图所示用我们手写的EventEmitter,替换上面例子中的events.EventEmitter,输出结果也是一样的。
总结
今天我们一起学习了EventEmitter的相关api,最后也手写了一个浏览器端简版的EventEmitter。回到实现前抛出的两个问题,其实不难发现EventEmitter正是采用了在前端非常常见的发布-订阅模式。发布-订阅模式的优点就是两个对象间不再是显性的调用,在完全不清楚双方细节的情况下依旧可以完成通信,实现代码的松耦合。但也不是一点缺点也没有,在弱化了对象间的通信的细节同时,如果过度使用,代码的可读性和易维护性也会有所降低!
转载自:https://juejin.cn/post/7062535050562109470