由于前不久做某厂的前端笔试题,遇到这道题没能实现出来,于是在此进行总结记录。

EventEmitterNode.js中提供的一个监听器类,类似于前端vue中的eventBus事件总线。

其原理主要是发布订阅者模式。

用订阅杂志进行类比,所有的杂志就是一个大对象: events: {}

  • 意林是其中一款杂志,那么意林就是events对象中的一个属性,值为数组(因为订阅意林杂志的人可以不止一个): events: {'意林': []}

  • 当我订阅意林,那么我就应该收入倒意林数组中,让意林知道我订阅了它: events: {'意林': ['我']} (这里’我’是执行函数)

  • 最后,当意林发布了新一期杂志时,那么我就会收到这一期杂志,也就是意林对数组中的’我’进行了执行

  • 当我觉得意林不好看了,那我就会取消订阅,于是乎在意林数组中将我移除即可

  • 如果我只想看下一期的意林,下下一期的意林我不想看了,那么我就进行单次订阅即可。(使用once方法)

下面这个`EventEmitter`类进行了简单实现,主要实现 'on'、'emit'、'once'和'remove'四个方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

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`

以上只是简单实现,如果有不正确的地方,还请各位大佬指正😀