前言
events模块一直是Node.js源码解读界的“网红”,许多博客和文章珠玉在前,但看着别人的思路一步步来总是不过瘾,这次试着自己读一读。漫无目的的阅读很可能看过就忘,所以先提2个问题给自己。
问题一:下面这段代码为什么不会死循环?
1 2 3 4 5 6 7
| const EventEmitter = require('events'); let emitter = new EventEmitter(); emitter.on('myEvent', function sth () { emitter.on('myEvent', sth); console.log('hi'); }); emitter.emit('myEvent');
|
问题二:event.once怎么实现的?
构造函数中初始化了几个相关变量。
1 2 3 4 5 6 7 8
| EventEmitter.init = function() { if (this._events === undefined || this._events === Object.getPrototypeOf(this)._events) { this._events = Object.create(null); this._eventsCount = 0; } this._maxListeners = this._maxListeners || undefined; };
|
在导出依赖时发现有一句
EventEmitter.EventEmitter = EventEmitter;
终于搞明白为啥经常看到一些库中的用法是
var eventEmitter = require('events').EventEmitter
原来是一样的,只是历史遗留问题。
然后是on
方法,on的实质是一个function(type,listener)
on方法源码也比较简单,处理了一些错误情况和可能引起的问题,最后将.events[type]的listener数组中加上新的listener,或者在没有的已存在的情况不创建数组直接赋值。值得注意是如果已存在的监听中有’newListener’的监听者,则需要先发送一个’newListener’事件。
接下来就是emit
方法
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
| EventEmitter.prototype.emit = function emit(type, ...args) { let doError = (type === 'error'); const events = this._events; if (events !== undefined) doError = (doError && events.error === undefined); else if (!doError) return false; const handler = events[type]; if (handler === undefined) return false;
if (typeof handler === 'function') { Reflect.apply(handler, this, args); } else { const len = handler.length; const listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) Reflect.apply(listeners[i], this, args); } return true; };
|
看了emit
方法后,就可以解释前文的第一个问题,当myEvent被emit后其实events[‘myEvent’]最后仅是创建了监听器数组。发现网上这个问题的几乎所有的答案都是else里的原因,通过数组clone了一个数组,没有操作原数组,所以不会出现死循环。看来真理还是掌握在少数人手里。
接下来看一下once方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function onceWrapper(...args) { if (!this.fired) { this.target.removeListener(this.type, this.wrapFn); this.fired = true; Reflect.apply(this.listener, this.target, args); } }
function _onceWrap(target, type, listener) { var state = { fired: false, wrapFn: undefined, target, type, listener }; var wrapped = onceWrapper.bind(state); wrapped.listener = listener; state.wrapFn = wrapped; return wrapped; }
EventEmitter.prototype.once = function once(type, listener) { if (typeof listener !== 'function') { const errors = lazyErrors(); throw new errors.ERR_INVALID_ARG_TYPE('listener', 'Function', listener); } this.on(type, _onceWrap(this, type, listener)); return this; };
|
其实就是通过闭包实现了一个名为fired的flag的私有,事件第一次被触发时,先调用removeListener,然后在运行对应的操作函数,并将fired设为true,下次就再也无法触发了,而且因为remove了这个监听器,所以之前的闭包也被回收了。
在once中的removeListener又是怎么实现的呢?
算了,不贴源码了,感觉很简单,就是将events[‘type’]中对应删掉,Object就delete,数组就spliceOne。其中这个spliceOne性能比Array自带的splice好很多。具体benchmark请自行搜索
1 2 3 4 5
| function spliceOne(list, index) { for (; index + 1 < list.length; index++) list[index] = list[index + 1]; list.pop(); }
|
结尾
events是Node.js的核心模块,Stream就是给予events实现的,其他很多模块又是基于Stream实现的,源码也比较简单,很容易理解,直接阅读源码也不会人云亦云地回答问题,对整个Node.js生态有了更好的理解。