由于前不久做某厂的前端笔试题,遇到这道题没能实现出来,于是在此进行总结记录。
EventEmitter
是Node.js
中提供的一个监听器类,类似于前端vue
中的eventBus
事件总线。
其原理主要是发布订阅者模式。
用订阅杂志进行类比,所有的杂志就是一个大对象: events: {}
意林是其中一款杂志,那么意林就是events对象中的一个属性,值为数组(因为订阅意林杂志的人可以不止一个): events: {'意林': []}
当我订阅意林,那么我就应该收入倒意林数组中,让意林知道我订阅了它: events: {'意林': ['我']}
(这里’我’是执行函数)
最后,当意林发布了新一期杂志时,那么我就会收到这一期杂志,也就是意林对数组中的’我’进行了执行。
当我觉得意林不好看了,那我就会取消订阅,于是乎在意林数组中将我移除即可。
如果我只想看下一期的意林,下下一期的意林我不想看了,那么我就进行单次订阅即可。(使用once方法)
下面这个EventEmitter
类进行了简单实现,主要实现 ‘on’、‘emit’、'once’和’remove’四个方法
class EventEmitter {
// 初始化事件对象
constructor() {
this.events = {}
}
// 事件监听,监听的过程就是订阅,也就是把订阅者收集起来
on(eventName, callback) {
// 如果不存在该事件,则进行数组初始化
if (!this.events[eventName]) {
this.events[eventName] = []
}
// 存在对应的数组继续订阅收集,则把事件推入收集数组
this.events[eventName].push(callback)
// 返回自身 方便链式调用
return this
}
// 事件触发,触发的过程就是发布,也就是通知订阅者
emit(eventName, ...args) {
// 不存在该事件,则不触发
if (!this.events[eventName]) {
return this
}
// 存在则对收集的订阅者一一通知(函数一一执行)
const fns = this.events[eventName]
// 执行的时候绑定自身this
fns.forEach(fn => fn.apply(this, args))
// 返回自身 方便链式调用
return this
}
// 解绑事件,取消订阅,将订阅者从订阅者数组中移除
remove(eventName, callback) {
if (!this.events[eventName]) {
return this;
}
// 没有指定解绑事件? 就是没有指定对应的订阅者,那么移除所有订阅者
if (!callback) {
this.events[eventName] = null
return this
}
// 否则找到该事件, 就是对应的订阅者,将其移除
const index = this.events[eventName].indexOf(callback);
this.events[eventName].splice(index, 1);
return this;
}
// 单次绑定事件,执行完后解绑
once(eventName, callback) {
const only = () => {
callback.apply(this, arguments);
this.remove(eventName, only);
};
this.on(eventName, only);
return this;
}
}
const emt = new EventEmitter()
// 订阅者1
const listener1 = function (...args) {
console.log('意林的第一个订阅者', ...args);
}
// 订阅者2
const listener2 = function (...args) {
console.log('意林的第二个订阅者', ...args);
}
// 收集订阅者,将杂志命名为'yilin'
emt.on('yilin', listener1)
emt.on('yilin', listener2)
// 500ms后订阅者1不想要该杂志了,进行取消订阅
setTimeout(() => {
emt.remove('yilin', listener1)
}, 500)
// 1秒后意林杂志更新了,进行发布,通知订阅者,这时由于订阅者1取消订阅了,所以订阅者1就不会执行了
setTimeout(() => {
emt.emit('yilin', 'hello world')
}, 1000)
// 打印结果为:1s后打印 `意林的第二个订阅者 hello world`
以上只是简单实现,如果有不正确的地方,还请各位大佬指正