events
模块的运用贯穿整个Node.js
, 读就Vans了。node
Events
模块用于解决那些问题?回调函数模式让
Node
能够处理异步操做,可是,为了适应回调函数,异步操做只能有两个状态:开始和结束。 对于那些多状态的异步操做(状态1,状态2,状态3, ....),回调函数就会没法处理。这是就必须将异步操做拆开, 分红多个阶段,每一个阶段结束时,调用回调函数。git
为了解决这个问题,Node
提供了EventEmitter
接口。经过事件,解决多状态异步操做的响应问题。github
发布订阅模式,是须要一个哈希表来存储监听事件和对应的回调函数的,在events
模块中,这个哈希表 形如:(多个回调函数存储为数组,若是没有回调函数,不会存在对应的键值)api
{
事件A: [回调函数1,回调函数2, 回调函数3],
事件B: 回调函数1
}
复制代码
全部API就是围绕这个哈希表进行增删改查操做数组
emitter.addListener(eventName, listener)
: 在哈希表中,对应事件中增长一个回调函数bash
emitter.on(eventName, listener)
: 同1,别名app
emitter.once(eventName, listener)
: 同1,单次监听器异步
emitter.prependListener(eventName, listener)
: 同1,添加在监听器数组开头函数
emitter.prependOnceListener(eventName, listener)
: 同1,添加在监听器数组开头 && 单次监听器源码分析
emitter.removeListener(eventName, listener)
: 移除指定的事件中的某个监听器
emitter.off(eventName, listener)
: 同上,别名
emitter.removeAllListeners([eventName])
: 移除所有监听器或者指定的事件的监听器
emitter.emit(eventName[, ...args])
: 按照监听器注册的顺序,同步地调用对应事件的监听器,并提供传入的参数
emitter.eventNames()
: 得到哈希表中全部的键值(包括Symbol
)
emitter.listenerCount(eventName)
: 得到哈希表中对应键值的监听器数量
emitter.listeners(eventName)
: 得到对应键的监听器数组的副本
emitter.rawListeners(eventName)
: 同上,只不过不会对once
处理事后的监听器还原(新增于Node 9.4.0
)
emitter.setMaxListeners(n)
: 设置当前实例监听器最大限制数的值
emitter.getMaxListeners()
: 返回当前实例监听器最大限制数的值
EventEmitter.defaultMaxListeners
: 它是每一个实例的监听器最大限制数的默认值,修改它会影响全部实例
此部分不会从头至尾的阅读源码,只是贴出源码中一些有趣的点!源码阅读会放在文末。
function EventEmitter() {
// 调用EventEmitter类的静态方法init初始化
// 我以为这样的初始化方式包装了代码的可读性,也提供了一个改写的方式
EventEmitter.init.call(this)
}
// export first
module.exports = EventEmitter
// 哈希表,保存一个EventEmitter实例中全部的注册事件和对应的处理函数
EventEmitter.prototype._events = undefined
// 计数器,表明当前实例中注册事件的个数
EventEmitter.prototype._eventsCount = 0
// 监听器最大限制数量的值
EventEmitter.prototype._maxListeners = undefined
// EventEmitter类的初始化静态方法
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
}
复制代码
为何使用Object.create(null)
而不是直接赋值{}
Object.create(null)
相对于{}
存在性能优点(因为Node版本的不一样,这里的性能优点也不能说是绝对的)
Object.craete(null)
更加干净, 对它的操做不会让对象受原型链影响
console.log({})
// 输出
{
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
}
console.log(Object.create(null))
// 输出
{}
复制代码
这样的代码会死循环吗?
emitter.on('lock', function lock() {
emitter.on('lock', lock)
})
复制代码
答案是不会,从简化的源码中分析:
EventEmitter.prototype.emit = function emit(type, ...args) {
const events = this._events;
const handler = events[type];
// 若是仅有一个回调函数
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)
}
}
// 复制数组嗷
function arrayClone(arr, n) {
var copy = new Array(n);
for (var i = 0; i < n; ++i)
copy[i] = arr[i];
return copy;
}
复制代码
假设lock
事件中的回调函数为[A, B, C]
, 那么若是不作处理,在执行过程当中会变成 [A, B, C, Lock, Lock, Lock, ....]
致使死循环,那么在循环以前,先复制一份当前 的监听器数组,那么该数组的长度就固定下来了,也就避免了死循环。
ES6
推出Reflect
以后,也基本没用过,而在Events
源码中有两处使用
Reflect.apply
: 对一个函数进行调用操做,同时能够传入一个数组做为调用参数。和Function.prototype.apply()
功能相似。 在源码中用于执行监听器
Reflect.ownKeys
: 返回一个包含全部自身属性(不包含继承属性)的数组。 在源码中用于获取哈希表中全部的事件
参考阮一峰ES6入门中: 将Object对象的一些明显属于语言内部的方法(好比Object.defineProperty),放到Reflect对象上。 现阶段,某些方法同时在Object和Reflect对象上部署,将来的新方法将只部署在Reflect对象上。 也就是说,从Reflect对象上能够拿到语言内部的方法。
// 返回已注册监听器的事件名数组
EventEmitter.prototype.eventNames = function eventNames() {
// 等价于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : [];
};
复制代码
这样使得代码更加易读!另外补上一个绕口令通常的存在
function test(a, b) {
return a + b
}
Function.prototype.apply.call(test, undefined, [1, 3]) // 4
Function.prototype.call.call(test, undefined, 1, 3) // 4
Function.prototype.call.apply(test, [undefined, 1, 3]); // 4
Function.prototype.apply.apply(test, [undefined, [1, 3]]); // 4
复制代码
源码
// 添加单次监听器
EventEmitter.prototype.once = function once(type, listener) {
// 参数检查
checkListener(listener);
// on是addEventListener的别名
this.on(type, _onceWrap(this, type, listener));
return this;
};
复制代码
从这里能够得出结论:对监听函数包装了一层!
// 参数分别表明: 当前events实例,事件名称,监听函数
function _onceWrap(target, type, listener) {
// 拓展this
// {
// fired: 标识位,是否应当移除此监听器
// wrapFn: 包装后的函数,用于移除监听器
// }
var state = { fired: false, wrapFn: undefined, target, type, listener };
var wrapped = onceWrapper.bind(state);
// 真正的监听器
wrapped.listener = listener;
state.wrapFn = wrapped;
// 返回包装后的函数
return wrapped;
}
function onceWrapper(...args) {
if (!this.fired) {
// 监听器会先被移除,而后再调用
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
Reflect.apply(this.listener, this.target, args);
}
}
复制代码
在EventEmitter#removeListener
这个api的实现里,须要从存储的监听器数组中去除一个元素,首先想到的就是Array#splice
这个api, 不过这个api提供的功能过于多了,它支持去除自定义数量的元素,还支持向数组中添加自定义的元素,因此,源码中选择本身实现一个最小可用的
所以你会在源码中看到
var splceOnce
EventEmitter.prototype.removeListener = function removeListener(type, listener) {
var events = this._events
var list = events[type]
// As of V8 6.6, depending on the size of the array, this is anywhere
// between 1.5-10x faster than the two-arg version of Array#splice()
// function spliceOne(list, index) {
// for (; index + 1 < list.length; index++)
// list[index] = list[index + 1];
// list.pop();
// }
if (spliceOne === undefined)
spliceOne = require('internal/util').spliceOne;
spliceOne(list, position);
}
复制代码
spliceOne,很好理解
function spliceOne(list, index) {
for (; index + 1 < list.length; index++)
list[index] = list[index + 1];
list.pop();
}
复制代码
修改EventEmitter.defaultMaxListeners
,会影响全部EventEmitter
实例,包括以前建立的
调用emitter.setMaxListeners(n)
,只会影响当前实例的监听器限制
限制不是强制的,有助于避免内存泄漏,超过限制只会输出警示信息。
相关源码
var defaultMaxListeners = 10
Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
enumerable: true,
get: function() {
return defaultMaxListeners;
},
set: function(arg) {
if (typeof arg !== 'number' || arg < 0 || Number.isNaN(arg)) {
const errors = lazyErrors();
throw new errors.ERR_OUT_OF_RANGE('defaultMaxListeners',
'a non-negative number',
arg);
}
defaultMaxListeners = arg;
}
});
复制代码
另外一部分
// 为指定的 EventEmitter 实例修改限制
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
if (typeof n !== 'number' || n < 0 || Number.isNaN(n)) {
const errors = lazyErrors();
throw new errors.ERR_OUT_OF_RANGE('n', 'a non-negative number', n);
}
this._maxListeners = n;
return this;
};
function $getMaxListeners(that) {
// 当前实例监听器限制的默认值为静态属性defaultMaxListeners的值
// 这也是为何修改它会影响全部的缘由
if (that._maxListeners === undefined)
return EventEmitter.defaultMaxListeners;
return that._maxListeners;
}
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
return $getMaxListeners(this);
};
复制代码