简单实现一个EventEmitter

EventEmitter 是 NodeJS 的核心模块 events 中的类,用于对 NodeJS 中的事件进行统一管理,用 events 特定的 API 对事件进行添加、触发和移除等等,核心方法的模式类似于发布订阅

1. Node 中的 EventEmitter

EventEmitter本质上是一个观察者模式的实现。

观察者模式:它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。

1
2
3
4
5
6
7
8
9
// Node 中的 EventEmitter 简单用法
let events = require('events');
let eventEmitter = new events.EventEmitter();

eventEmitter.on('show', function () {
console.log('this is show callback');
})

eventEmitter.emit('show');

eventEmitter是EventEmitter模块的一个实例,eventEmitter的emit方法,发出show事件,通过eventEmitter的on方法监听,从而执行相应的函数。

2. 简单实现一个 EventEmitter

接下来我们简单实现一个EventEmitter模块的基础功能emit和on。

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
class EventEmitter {
constructor () {
this._events = {}; // 保存事件
}

/**
* 添加事件监听
* @param {String} eventName 监听的对象名称
* @param {Function} callback 事件处理函数
*/
on (eventName, callback) {
if (!this._events) {
this._events = {};
}

if (!this._events[eventName]) {
this._events[eventName] = []; // 每个监听的对象,要处理的事件存放在一个数组里
}

this._events[eventName].push(callback); // 添加监听事件
}

/**
* 触发事件监听函数
* @param {String} eventName 监听的对象名称
* @param {...[type]} args 传入的参数
*/
emit (eventName, ...args) {
if (this._events[eventName]) {
for (var i = 0; i < this._events[eventName].length; i++) {
this._events[eventName][i](...args);
}
}
}

/**
* 删除事件监听
* @param eventName
* @param callback
*/
off (eventName, callback) {
if (callback) {
this._events[eventName] = this._events[eventName].filter(fn => fn !== callback);
} else {
delete this._events[eventName];
}
}

/**
* 只执行一次事件监听
* @param eventName
* @param callback
*/
once (eventName, callback) {
let onceFun = (...args) => {
callback.apply(this, args);
this.off(eventName, onceFun);
}
this.on(eventName, onceFun);
}
}

3. EventEmitter 模块的异常处理

为什么要添加异常处理模块?因为node中有一个特殊的事件error,如果异常没有被捕获,就会触发 process 的 uncaughtException 事件抛出,如果没有注册该事件的监听器,则 Node.js 会在控制台打印该异常的堆栈信息,并结束进程。

1. try…catch 方法

通过 try…catch 可以来捕获错误:

1
2
3
4
5
try {
let x = x;
} catch (e) {
console.log(e);
}

如上,赋值语句的错误会被捕获,但是try…catch不能捕获非阻塞或者异步函数里的异常

1
2
3
4
5
6
7
try {
process.nextTick(function () {
let x = x;
})
} catch (e) {
console.log(e);
}

如上,因为process.nextTick是异步的,因此在process.nextTick内部的错误不能被捕获。

2. process.on(‘uncaughtException’) 方法

node中提供了一个最外层的兜底的捕获异常的方法。非阻塞或者异步函数中的异常都会抛出到最外层,如果异常没有被捕获,那么会暴露出来,被最外层的process.on(‘uncaughtException’)所捕获。

1
2
3
4
5
6
7
8
9
try {
process.nextTick(function(){
let x = x; //第二个x在使用前未定义,会抛出异常
},0);
} catch (e) {
console.log('该异常已经被捕获');
console.log(e);
}
process.on('uncaughtException',function(err){console.log(err)})

4. EventEmitter 源码实现

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

/**
* EventEmitter 构造函数
*/
function EventEmitter () {
//私有属性,保存订阅方法
this._events = Object.create(null);
};

// 默认最大监听数
EventEmitter.defaultMaxListeners = 10;

/**
* on方法,该方法用于订阅事件,在旧版本的node.js中是addListener方法,它们是同一个函数
* @param {String} type 监听的对象名称
* @param {Function} listener 监听的回调函数
* @param {Boolean} flag 是否先于其他同类监听事件执行
*/
EventEmitter.prototype.on = EventEmitter.prototype.addListener = function (type, listener, flag) {
//保证存在实例属性
if (!this._events) this._events = Object.create(null);

if (this._events[type]) {
if (flag) { //从头部插入
this._events[type].unshift(listener);
} else {
this._events[type].push(listener);
}
} else {
this._events[type] = [listener];
}
//绑定事件,触发newListener
if (type !== 'newListener') {
this.emit('newListener', type);
}
};

/**
* emit方法: 将订阅方法取出执行,使用call方法来修正this的指向,使其指向子类的实例。
* @param {String} type 监听的对象名称
* @param {...[type]} args 参数传递
*/
EventEmitter.prototype.emit = function (type, ...args) {
if (this._events[type]) {
this._events[type].forEach(fn => fn.call(this, ...args));
}
};

/**
* once方法,它的功能是将事件订阅“一次”,当这个事件触发过就不会再次触发了。
* 其原理是将订阅的方法再包裹一层函数,在执行后将此函数移除即可。
* @param {String} type 监听的对象名称
* @param {Function} listener 监听的回调函数
*/
EventEmitter.prototype.once = function (type, listener) {
let _this = this;

// 中间函数,在调用完之后立即删除订阅
function only() {
listener();
_this.removeListener(type, only);
}
// origin保存原回调的引用,用于remove时的判断
only.origin = listener;
this.on(type, only);
};

/**
* off方法即为退订,原理同观察者模式一样,将订阅方法从数组中移除即可。
* @param {String} type 监听的对象名称
* @param {Function} listener 监听的回调函数
*/
EventEmitter.prototype.off = EventEmitter.prototype.removeListener = function (type, listener) {

if (this._events[type]) {
// 过滤掉退订的方法,从数组中移除
this._events[type] = this._events[type].filter(fn => {
return fn !== listener && fn.origin !== listener
});
}
};