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);
};
複製代碼