深入理解 Node.js 中 EventEmitter源码分析(3.0.0版本)
events模塊對外提供了一個 EventEmitter 對象,即:events.EventEmitter. EventEmitter 是NodeJS的核心模塊events中的類,用于對NodeJS中的事件進行統一管理,使用events可以對特定的API事件進行添加,觸發和移除等。
我們可以通過 require('events')來訪問該模塊。
比如如下代碼:
// 引入 events 模塊 const events = require('events');console.log(events);// 創建 eventEmitter 對象 const eventEmitter = new events.EventEmitter();console.log(eventEmitter);我們先把如上代碼放入 main.js 里面,然后在 項目中對應目錄下 執行 node main.js 執行結果如下:
如上圖可以看到,console.log(events); 打印后,有 EventEmitter 屬性,defaultMaxListeners 屬性(getter/setter) 方法,init函數屬性,及 listenerCount 函數屬性等。
1. defaultMaxListeners的含義是:默認事件最大監聽個數,在源碼中 默認是10個,設置最大監聽個數為10個,因為如果監聽的個數過多的話,會導致 內存泄露的問題產生。因此默認設置了十個。當然我們在源碼中可以設置或者獲取最大的監聽個數,我們可以設置最大監聽的個數 如下代碼:
// Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.');}this._maxListeners = n;return this; };當然有設置監聽的個數,我們也有獲取最大的監聽個數,如下源碼:
function $getMaxListeners(that) {if (that._maxListeners === undefined)return EventEmitter.defaultMaxListeners;return that._maxListeners; }EventEmitter.prototype.getMaxListeners = function getMaxListeners() {return $getMaxListeners(this); };當然源碼中也使用了 Object.defineProperty方法監聽該屬性值是否發生改變,如下基本源碼:
Object.defineProperty(EventEmitter, 'defaultMaxListeners', {enumerable: true,get: function() {return defaultMaxListeners;},set: function(arg) {if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.');}defaultMaxListeners = arg;} });如果defaultMaxListeners值發生改變的話,就會調用相對應的getter/setter方法。
2. events 中的init函數中基本源碼如下:
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; };如上基本代碼:this._events 的含義是 保存所有的事件對象,事件的觸發和事件的移除操作監聽等都在
這個對象_events的基礎上實現。
this._eventsCount 的含義是:用于統計事件的個數,也就是_events對象有多少個屬性。
this._maxListeners 的含義是:保存最大的監聽數的含義。
3. event中的listenerCount函數的作用是:返回指定事件的監聽器數量。
如下基本代碼:
4. events中的EventEmitter屬性
該屬性就是events對外提供的一個對象類,使用 EventEmitter 類就是對事件的觸發和監聽進行封裝。
現在我們看下創建 eventEmitter 對象,如下代碼吧:
// 創建 eventEmitter 對象 const eventEmitter = new events.EventEmitter();console.log(eventEmitter);打印信息如下:
可以看到該實列有 _events屬性及_maxListeners屬性,及該實列上的原型有很多對應的方法,比如 addListener, emit,listenerCount, listeners, on, once, removeAllListeners, removeListener, setMaxListeners 等方法,及_events 及 _maxListeners 等屬性。我們先來看下一些簡單實列上的屬性的基本源碼如下:
// Backwards-compat with node 0.10.x EventEmitter.EventEmitter = EventEmitter;EventEmitter.prototype._events = undefined; EventEmitter.prototype._eventsCount = 0; EventEmitter.prototype._maxListeners = undefined;一:EventEmitter基本的API使用:
1. addListener(event, listener) 為指定的事件注冊一個監聽器。該方法是on的別名。該方法接收一個事件名和一個回調函數。
如下代碼演示:
2. listenerCount(eventName)
該方法返回注冊了指定事件的監聽數量。基本語法如下:
EventEmitter.listenerCount(eventName);
eventName: 指監聽的事件名
如下基本代碼:
// 引入 events 模塊 const events = require('events');console.log(events);// 創建 eventEmitter 對象 const eventEmitter = new events.EventEmitter();console.log(eventEmitter);// 注冊 kongzhi 事件 eventEmitter.on('kongzhi', function() {console.log('dddd1111'); });// 注冊 kongzhi 事件 eventEmitter.on('kongzhi', function() {console.log('dddd22222'); });// 觸發kongzhi事件 eventEmitter.emit('kongzhi');const num = eventEmitter.listenerCount('kongzhi'); console.log(num); // 返回2 說明注冊了兩個 kongzhi 這個事件比如源碼中如下代碼所示:
EventEmitter.prototype.listenerCount = listenerCount; function listenerCount(type) {var events = this._events;if (events !== undefined) {var evlistener = events[type];if (typeof evlistener === 'function') {return 1;} else if (evlistener !== undefined) {return evlistener.length;}}return 0; }如上代碼,先判斷 this._events 是否保存了事件,如果保存了事件的話,如果它是個函數的話,那么 return 1; 否則的話,如果不等于undefined,直接返回該監聽事件名的長度。其他的情況下 返回 0;
因此上面我們通過如下代碼,就可以獲取到該監聽的事件的長度了:如下代碼:
const num = eventEmitter.listenerCount('kongzhi'); console.log(num); // 返回2 說明注冊了兩個 kongzhi 這個事件3. listeners(event)
該方法返回指定事件的監聽器數組。
如下代碼演示:
// 引入 events 模塊 const events = require('events');console.log(events);// 創建 eventEmitter 對象 const eventEmitter = new events.EventEmitter();console.log(eventEmitter);// 注冊 kongzhi 事件 eventEmitter.on('kongzhi', function() {console.log('dddd1111'); });// 注冊 kongzhi 事件 eventEmitter.on('kongzhi', function() {console.log('dddd22222'); });// 觸發kongzhi事件 eventEmitter.emit('kongzhi');const num = eventEmitter.listeners('kongzhi'); console.log(num); // 返回監聽事件的數組 num.forEach((func) => {func(); // 我們可以這樣指定任何一個事件調用,然后會分別打印 dddd1111, dddd2222 });4. once(event, listener)
該函數的含義是:為指定事件注冊一個單次監聽器,也就是說監聽器最多只會觸發一次,觸發后立刻解除該監聽器。
如下代碼:
如上代碼所示:使用on 事件注冊的話,如果使用emit觸發的話,觸發了多少次,就執行多少次,使用once注冊事件的話,emit觸發多次的話,最后也只能調用一次,如下執行結果如下:
5. removeListener(eventName, listener)
該方法的作用是:移除指定事件的某個監聽器。監聽器必須是該事件已經注冊過的監聽器。
eventName: 事件名稱
listener: 回調函數的名稱
如下代碼演示:
// 引入 events 模塊 const events = require('events');console.log(events);// 創建 eventEmitter 對象 const eventEmitter = new events.EventEmitter();console.log(eventEmitter);const callback = function() {console.log(11111); }; // 使用on注冊監聽器 eventEmitter.on('kongzhi', callback);// 觸發事件 kongzhi eventEmitter.emit('kongzhi');eventEmitter.removeListener('kongzhi', callback);/* 我們繼續觸發事件 kongzhi 是不會觸發的,因為上面已經使用 removeListener 已經刪除了 kongzhi 事件了*/ eventEmitter.emit('kongzhi');6. removeAllListeners([event])
移除所有事件的所有監聽器,如果我們指定事件的話,則是移除指定事件的所有監聽器。
參數event: 該參數的含義是事件名稱,如果指定了該事件名稱的話,則會刪除該指定的事件名稱對應的所有函數,如果沒有指定任何事件名的話,則是刪除所有的事件。如下代碼所示:
// 引入 events 模塊 const events = require('events');console.log(events);// 創建 eventEmitter 對象 const eventEmitter = new events.EventEmitter();console.log(eventEmitter);const callback = function() {console.log(11111); }; // 使用on注冊監聽器 eventEmitter.on('kongzhi', callback);// 注冊kongzhi2事件 eventEmitter.on('kongzhi2', callback);// 觸發事件 kongzhi 是可以觸發的 eventEmitter.emit('kongzhi');// 刪除所有的監聽器 eventEmitter.removeAllListeners();/* 我們繼續觸發事件 kongzhi 和 kongzhi2 是不會觸發的,因為上面已經使用 removeAllListeners 已經刪除了 所有的 事件了*/ eventEmitter.emit('kongzhi');eventEmitter.emit('kongzhi2');7. setMaxListeners(n)
該方法的作用是 設置監聽器的默認數量。因為EventEmitters默認的監聽器為10個,如果超過10個就會輸出警告信息,因此使用該方法,可以設置監聽器的默認數量, 使之最大的數量不會報錯。
如下代碼所示:
// 引入 events 模塊 const events = require('events');console.log(events);// 創建 eventEmitter 對象 const eventEmitter = new events.EventEmitter();console.log(eventEmitter);const callback = function() {console.log(11111); };// 設置默認監聽數量最多為1個,如果超過該數量,控制臺會發出警告 eventEmitter.setMaxListeners(1);// 使用on注冊監聽器 eventEmitter.on('kongzhi', callback);// 注冊kongzhi事件 eventEmitter.on('kongzhi', callback);// 觸發事件 kongzhi eventEmitter.emit('kongzhi');執行結果如下所示:
當我們把上面的eventEmitter.setMaxListeners(2); 設置為大于1的時候,就不會報錯了,比如最大設置的默認數量為2,監聽器最大為2,就不會報錯了。
如下圖所示:
二:EventEmitter 源碼分析
EventEmitter類它實質是一個觀察者模式的實現,什么是觀察者模式呢?觀察者模式定義了對象間的一種一對多的關系,它可以讓多個觀察者對象同時監聽一個主題對象,當一個主題對象發生改變時,所有依賴于它的對象都會得到一個通知。
那么在觀察者模式中最典型的實列demo就是 EventEmitter類中on和emit方法,我們可以通過on注冊多個事件進行監聽,而我們可以通過emit方法來對on事件進行觸發。比如如下demo代碼:
如上代碼,我們通過eventEmitter的emit方法,發出 kongzhi 事件,然后我們通過 eventEmitter 的on方法進行監聽,從而執行相對應的函數,從而打印出信息出來。其中on方法屬于多個觀察者那個對象,它可以有多個on方法進行監聽 emit中觸發的那個主題對象,當那個emit觸發的主體對象發生改變時,所有的on方法監聽的對象都會觸發。
上面我們知道了 EventEmitter模塊的on和emit的用途后,我們首先來實現一個包含 emit和on方法的EventEmitter類。
如下代碼:
如上代碼就實現了一個類為 EventEmitter 中的on和emit方法了,on是注冊事件,emit是觸發該事件,然后執行的對應的回調函數,首先通過on去注冊事件,然后我們會通過 this._event對象來保存該事件的回調函數,this._event中對象的key就是on注冊的事件名,然后on注冊的回調函數就是 this._event中使用數組保存起來,該this._event的值就是一個數組函數,然后在emit方法中,使用for循環進行依次執行該回調函數。就會依次觸發對應的函數了。
emit(eventName, ...arg) 方法傳入的參數,第一個為事件名,其他參數事件對應執行函數中的實參。該方法的作用是:從事件對象中,尋找對應的key為eventName的屬性,執行該屬性所對應的數組里面的每一個函數。
上面是一個簡單的實現,下面我們再來看看 events.js 源碼中是如何實現的呢?
events.js 源碼分析:
function EventEmitter() {EventEmitter.init.call(this); } module.exports = EventEmitter;EventEmitter.EventEmitter = EventEmitter;EventEmitter.prototype._events = undefined; EventEmitter.prototype._eventsCount = 0; EventEmitter.prototype._maxListeners = undefined;EventEmitter.init = function() {/* 如果this._event為undefined,或 Object.getPrototypeOf 上面也沒有 events的話(es5),則使用Object.create()創建一個空的對象. 并且設置_eventsCount事件個數為0,也就是初始化。并且初始化 _maxListeners,默認為10個,未初始化之前是undefined。*/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.init方法進行初始化,然后使用 this._events = Object.create(null);創建一個對象保存到 this._events中,作用是用于存儲和統一管理所有類型的事件,在創建構造函數的時候導出了 EventEmitter,后面所有的方法放在該對象中的原型中。_maxListeners的含義我們上面已經講解過,是保存最大的監聽數。默認為10個。
當然我們可以在源碼中使用 getMaxListeners/setMaxListeners方法設置最大的監聽數,比如如下的源碼:
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.');}this._maxListeners = n;return this; };function $getMaxListeners(that) {if (that._maxListeners === undefined)return EventEmitter.defaultMaxListeners;return that._maxListeners; }EventEmitter.prototype.getMaxListeners = function getMaxListeners() {return $getMaxListeners(this); };在源碼中,我們也對 最大的監聽數使用了 Object.defineProperty方法進行監聽,如下源碼所示:
var defaultMaxListeners = 10;Object.defineProperty(EventEmitter, 'defaultMaxListeners', {enumerable: true,get: function() {return defaultMaxListeners;},set: function(arg) {if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.');}defaultMaxListeners = arg;} });2. addEventListener 或 on 添加事件源碼如下:
/*addListener函數的別名是on,該函數有兩個參數,type是事件名稱,listener是監聽函數。之后會調用 _addListener函數進行初始化,從_addListener函數中返回的是this,這樣的設計目的是可以進行鏈式調用。 */ EventEmitter.prototype.addListener = function addListener(type, listener) {return _addListener(this, type, listener, false); };EventEmitter.prototype.on = EventEmitter.prototype.addListener; /*該_addListener函數有四個參數,分別為 target指向了當前this對象。type為事件名稱,listener為監聽事件的函數名。prepend參數如果為true的話,是把監聽函數插入到數組的頭部,默認為false,插入到數組的尾部。 */ function _addListener(target, type, listener, prepend) {var m;var events;var existing;/*如果使用on或addEventListener注冊事件時,如果listener參數不是一個函數的話,會拋出錯誤。如下代碼。*/if (typeof listener !== 'function') {throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);}/*1. 拿到當前的所有的事件 _events, 即拿到一個事件對象,對象中存放了觸發事件組或空對象。如果this._events是undefined的話,就使用 Object.create(null) 創建一個空對象給events保存起來。然后設置 this._eventsCount = 0; 用于統計事件的個數,也就是_events對象有多少個屬性。2. 如果有this._event的話,如果使用了on或addEventListener注冊了 newListener 事件的話,就直接觸發執行它。*/events = target._events;if (events === undefined) {events = target._events = Object.create(null);target._eventsCount = 0;} else {// To avoid recursion in the case that type === "newListener"! Before// adding it to the listeners, first emit "newListener".if (events.newListener !== undefined) {target.emit('newListener', type,listener.listener ? listener.listener : listener);// Re-assign `events` because a newListener handler could have caused the// this._events to be assigned to a new object/*重新注冊events,因為 newListener鉤子可能導致this._event去重新注冊個新對象。*/events = target._events;}// 保存上一次觸發的事件existing = events[type];}/*exiting變量是保存上一次觸發的事件對象,比如:如下測試代碼:// 引入 events 模塊const events = require('events');console.log(events);// 創建 eventEmitter 對象const eventEmitter = new events.EventEmitter();console.log(eventEmitter);const callback = function() {console.log(11111);};// 使用on注冊監聽器eventEmitter.on('kongzhi', callback);// 注冊kongzhi2事件eventEmitter.on('kongzhi', callback);// 觸發事件 kongzhi 是可以觸發的eventEmitter.emit('kongzhi');如上基本代碼,當我們第一次監聽eventEmitter.on('kongzhi')的時候,existing并沒有保存該事件對象函數,因此第一次的時候為undefined,所以會做如下判斷 如果為undefined的話,就把 callback函數賦值給 events[type]了。然后 target._eventsCount 自增1,也就是說 _eventsCount 保存的事件個數加1.2. 如果 typeof existing 是個函數的話,說明之前注冊過一次 'kongzhi' 這樣的事件,然后繼續判斷 prepend 是否為true還是false,為true的話,說明把該函數插入到數組的最前面,否則的話,插入該數組的后面。如果為true,如下代碼:events[type] = [listener, existing],否則為false的話,events[type] = [existing, listener], 其中existing是保存上一次觸發的事件對象。然后所有的判斷完成后,把最新值重新賦值給 existing 變量。3. 如果 existing 已經是個數組的話,并且prepend為true的話,直接插入到數組的最前面, 因此執行代碼:existing.unshift(listener);4. 其他的情況就是 prepend 默認為false的情況下,且 existing 為數組的情況下,直接把 監聽函數 listener 插入到existing該數組的后面去,如下代碼: existing.push(listener);*/if (existing === undefined) {// Optimize the case of one listener. Don't need the extra array object.existing = events[type] = listener;++target._eventsCount;} else {if (typeof existing === 'function') {// Adding the second element, need to change to array.existing = events[type] =prepend ? [listener, existing] : [existing, listener];// If we've already got an array, just append.} else if (prepend) {existing.unshift(listener);} else {existing.push(listener);}/*下面是監聽函數最大的數量。如果監聽的數量 m > 0 && existing.length(監聽數量的長度大于監聽的數量的話) > m&& !existing.warned 的話。existing.warned 設置為false,目的是打印一次錯誤即可,因此函數內部代碼直接設置為 existing.warned = true;比如如下代碼:// 引入 events 模塊const events = require('events');console.log(events);// 創建 eventEmitter 對象const eventEmitter = new events.EventEmitter();console.log(eventEmitter);const callback = function() {console.log(11111);};// 設置默認監聽數量最多為1個,如果超過該數量,控制臺會發出警告eventEmitter.setMaxListeners(1);// 使用on注冊監聽器eventEmitter.on('kongzhi', callback);// 注冊kongzhi事件eventEmitter.on('kongzhi', callback);// 觸發事件 kongzhieventEmitter.emit('kongzhi');設置監聽最大個數為1個,但是實際監聽的個數為2,因此在控制臺中會輸出錯誤,如下報錯信息:(node) warning: possible EventEmitter memory leak detected. 2 listeners added. Use emitter.setMaxListeners() to increase limit.如上錯誤就是下面的代碼的new Error拋出的。最后通過 執行如下函數拋出,如下代碼:function ProcessEmitWarning(warning) {if (console && console.warn) console.warn(warning);}*/// Check for listener leakm = $getMaxListeners(target);if (m > 0 && existing.length > m && !existing.warned) {existing.warned = true;// No error code for this since it is a Warning// eslint-disable-next-line no-restricted-syntaxvar w = new Error('Possible EventEmitter memory leak detected. ' +existing.length + ' ' + String(type) + ' listeners ' +'added. Use emitter.setMaxListeners() to ' +'increase limit');w.name = 'MaxListenersExceededWarning';w.emitter = target;w.type = type;w.count = existing.length;ProcessEmitWarning(w);}}return target; }3. emit 觸發事件的源碼如下:
/*1. 定義一個args數組,來保存所有的emit后面的參數。比如如下代碼:// 引入 events 模塊const events = require('events');console.log(events);// 創建 eventEmitter 對象const eventEmitter = new events.EventEmitter();console.log(eventEmitter);// 注冊 kongzhi 事件eventEmitter.on('kongzhi', function(age) {console.log('dddd' + age); // 打印 dddd30});// 觸發kongzhi事件 eventEmitter.emit('kongzhi', 30);如上代碼,emit上的第二個參數傳遞了30,因此在on監聽該對象的時候,該函數會接收age這個參數值為30,因此會打印dddd30.如下代碼:for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); 就是從第一個參數開始,使用 arguments.length 獲取函數中所有的參數,依次循環保存到 args數組里面去。注意這邊 i 是從1開始的,那么位置0就是type(類型)。除了觸發類型之后所有的變量,也就是所有的參數保存到 args數組內部。2. 然后判斷 type 是否等于 error, 如 var doError = (type === 'error'); 這句代碼會返回一個布爾值。var events = this._events;保存所有的事件對象,如果 type === ‘error’的話,比如 doError 為true的話,如果args數組保存的第一個值是Error的實列的話,就拋出該error。否則的話,就拋出如下錯誤信息:events.js:62 Uncaught Error: Uncaught, unspecified "error" event. (undefined)如下代碼使用 emit來監聽 error事件會打印如上的error代碼的。如下:// 引入 events 模塊const events = require('events');console.log(events);// 創建 eventEmitter 對象const eventEmitter = new events.EventEmitter();console.log(eventEmitter);eventEmitter.emit('error');如果觸發emit的事件不是error的話,那么獲取該事件的對象,如下代碼:var handler = events[type];如果該事件對象沒有函數的話,直接返回,監聽對象 on中會報錯 listener 必須為一個函數的錯誤。如果該handler是一個函數的話,就會執行這個函數。如:ReflectApply(handler, this, args);代碼;ReflectApply 封裝的源碼在events.js中的最上面代碼:var R = typeof Reflect === 'object' ? Reflect : nullvar ReflectApply = R && typeof R.apply === 'function'? R.apply: function ReflectApply(target, receiver, args) {return Function.prototype.apply.call(target, receiver, args);}想要了解 Reflect.apply的方法的話,可以看我這篇文章 (https://www.cnblogs.com/tugenhua0707/p/10291909.html#_labe2). 因此就能直接執行該回調函數。2. 如果handler不是一個函數的話,而是一個數組的話,那么就循環該數組,然后依次執行對應的函數。 */ EventEmitter.prototype.emit = function emit(type) {var args = [];for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);var doError = (type === 'error');var events = this._events;if (events !== undefined)doError = (doError && events.error === undefined);else if (!doError)return false;// If there is no 'error' event listener then throw.if (doError) {var er;if (args.length > 0)er = args[0];if (er instanceof Error) {// Note: The comments on the `throw` lines are intentional, they show// up in Node's output if this results in an unhandled exception.throw er; // Unhandled 'error' event }// At least give some kind of context to the uservar err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : ''));err.context = er;throw err; // Unhandled 'error' event }var handler = events[type];if (handler === undefined)return false;if (typeof handler === 'function') {ReflectApply(handler, this, args);} else {var len = handler.length;var listeners = arrayClone(handler, len);for (var i = 0; i < len; ++i)ReflectApply(listeners[i], this, args);}return true; };4. 刪除指定的事件監聽器removeListener源碼如下:
// 如下基本代碼:removeListener的別名是off。 EventEmitter.prototype.removeListener = function removeListener(type, listener) {var list, events, position, i, originalListener;/* 如果removeListener方法中第二個參數不是一個函數的話,拋出錯誤。必須為一個函數。*/if (typeof listener !== 'function') {throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);}/*獲取所有的事件對象,保存到 events變量內。如果事件對象是undefined的話,直接返回。然后該事件對象 list = events[type]; type 是要被刪除的事件名,因此 list 就是保存對應的函數了。1. 如果list對象函數等于 listener 要刪除的對象函數的話,或者 list.listener === listener 的話;或者匹配的是監聽事件key: Function如下代碼:// 引入 events 模塊const events = require('events');console.log(events);// 創建 eventEmitter 對象const eventEmitter = new events.EventEmitter();console.log(eventEmitter);const callback = function() {console.log(11111);};// 使用on注冊監聽器eventEmitter.on('kongzhi', callback);// 觸發事件 kongzhieventEmitter.emit('kongzhi');eventEmitter.removeListener('kongzhi', callback);/* 我們繼續觸發事件 kongzhi 是不會觸發的,因為上面已經使用 removeListener 已經刪除了 kongzhi 事件了*/eventEmitter.emit('kongzhi');*/events = this._events;if (events === undefined)return this;list = events[type];if (list === undefined)return this;if (list === listener || list.listener === listener) {/*如果刪除的事件相等的話,--this._eventsCount 自減1. 同時判斷 this._eventsCount 如果等于0的話,則移除所有監聽,這里重置監聽對象數組。即 this._events = Object.create(null);*/if (--this._eventsCount === 0)this._events = Object.create(null);else {/*正常執行刪除:delete events[type].在頁面上我們可以來監聽 removeListener 事件,比如使用 removeListener 刪除事件,我們可以使用on來監聽 removeListener 事件,比如如下代碼:// 引入 events 模塊const events = require('events');console.log(events);// 創建 eventEmitter 對象const eventEmitter = new events.EventEmitter();console.log(eventEmitter);const callback = function() {console.log(11111);};// 使用on注冊監聽器eventEmitter.on('kongzhi', callback);// 觸發事件 kongzhieventEmitter.emit('kongzhi');eventEmitter.on('removeListener', function(type, listener) {console.log('這里是來監聽刪除事件的'); // 會打印出來console.log(type); // kongzhiconsole.log(listener); // callback對應的函數});eventEmitter.removeListener('kongzhi', callback);/* 我們繼續觸發事件 kongzhi 是不會觸發的,因為上面已經使用 removeListener 已經刪除了 kongzhi 事件了*/eventEmitter.emit('kongzhi');*/delete events[type];if (events.removeListener)this.emit('removeListener', type, list.listener || listener);}} else if (typeof list !== 'function') {/*這里是其他情況,如果list監聽的不是一個函數的話,而是一個數組的話,比如on監聽的多個相同的事件名的時候,則遍歷該數組,同樣判斷,如果該數組的任何一項等于被刪除掉的 listener函數的話,或者數組中的任何一項的key===listener 等于被刪除掉的listener函數的話,使用 originalListener = list[i].listener; 保存起來,同樣使用 position = i; 保存該對應的位置。跳出for循環。2. if (position < 0) 如果該 position 小于0的話,直接返回。說明沒有要刪除的事件。3. 如果position === 0的話,則刪除數組中的第一個元素,使用 list.shift(),移除數組中的第一個元素。4. 否則的話調用 spliceOne 方法,刪除數組list中的對應位置的元素,該spliceOne方法代碼如下:function spliceOne(list, index) {for (; index + 1 < list.length; index++)list[index] = list[index + 1];list.pop();}*/position = -1;for (i = list.length - 1; i >= 0; i--) {if (list[i] === listener || list[i].listener === listener) {originalListener = list[i].listener;position = i;break;}}if (position < 0)return this;if (position === 0)list.shift();else {spliceOne(list, position);}// 如果list.length === 1 的話,events[type] = list[0]. if (list.length === 1)events[type] = list[0];if (events.removeListener !== undefined)this.emit('removeListener', type, originalListener || listener);}return this; };EventEmitter.prototype.off = EventEmitter.prototype.removeListener;5. 刪除所有的監聽器 removeAllListeners 源碼如下:
/*removeAllListeners該函數接收一個參數type,事件名稱。 */ EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) {var listeners, events, i;events = this._events;// 如果 保存的事件對象 events 為undefined的話,直接返回if (events === undefined)return this;// not listening for removeListener, no need to emit/*1. 如果 this._events保存的事件對象的removeListener為undefined的話。說明沒有使用 removeListener來進行刪除事件。2. 如果 arguments.length === 0 則是刪除所有的事件。因為如果removeAllListeners沒有指定事件名稱的話,則是刪除所有的事件的。因此直接設置 this._events = Object.create(null); 為空對象,并且 this._eventsCount = 0; 監聽的事件數量為0.3. 如果 events[type] !== undefined 不等于undefined,說明type有的話,則是刪除該事件名的所有事件。--this._eventsCount 自減1. 并且如果 this._eventsCount === 0 等于0 的話,則:this._events = Object.create(null); 設置空對象。4. 如果 this._eventsCount 不等于0 的話,則刪除該指定的事件的所有事件名稱。最后返回該this對象。*/if (events.removeListener === undefined) {if (arguments.length === 0) {this._events = Object.create(null);this._eventsCount = 0;} else if (events[type] !== undefined) {if (--this._eventsCount === 0)this._events = Object.create(null);elsedelete events[type];}return this;}// emit removeListener for all listeners on all events/*1. 如果 arguments.length === 0 等于0的話,則是刪除所有的事件。先使用 var keys = Object.keys(events);獲取所有的keys。然后使用for循環依次遍歷,得到某一個事件key。然后依次使用 this.removeAllListeners(key);方法遞歸調用刪除對應的key。2. 最后執行 this._events = Object.create(null); 設置為空對象。置空。3. this._eventsCount = 0; 事件的個數設置為0. 最后返回該this對象。*/if (arguments.length === 0) {var keys = Object.keys(events);var key;for (i = 0; i < keys.length; ++i) {key = keys[i];if (key === 'removeListener') continue;this.removeAllListeners(key);}this.removeAllListeners('removeListener');this._events = Object.create(null);this._eventsCount = 0;return this;}/*1. 如果該 events[type] 是一個函數的話,則調用 this.removeListener(type, listeners); 刪除該事件對應的函數。2. 其他的情況就是 listeners 不等于 undefined的話。說明是一個數組的話,那么依次循環該數組,然后依次使用removeListener方法刪除該事件對應的函數。最后返回this對象。使可以鏈式調用。*/listeners = events[type];if (typeof listeners === 'function') {this.removeListener(type, listeners);} else if (listeners !== undefined) {// LIFO orderfor (i = listeners.length - 1; i >= 0; i--) {this.removeListener(type, listeners[i]);}}return this; };6. 返回指定事件的監聽器數組--listeners源碼如下:
function _listeners(target, type, unwrap) {var events = target._events;if (events === undefined)return [];var evlistener = events[type];if (evlistener === undefined)return [];/*1. 如果events[type]是一個函數的話,就返回 [evlistener.listener || evlistener], 則返回該數組函數。2. 否則的話,則返回 該數組函數。*/if (typeof evlistener === 'function')return unwrap ? [evlistener.listener || evlistener] : [evlistener];return unwrap ?unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); } function unwrapListeners(arr) {var ret = new Array(arr.length);for (var i = 0; i < ret.length; ++i) {ret[i] = arr[i].listener || arr[i];}return ret; } function arrayClone(arr, n) {var copy = new Array(n);for (var i = 0; i < n; ++i)copy[i] = arr[i];return copy; } EventEmitter.prototype.listeners = function listeners(type) {return _listeners(this, type, true); };如下代碼演示:
// 引入 events 模塊 const events = require('events');console.log(events);// 創建 eventEmitter 對象 const eventEmitter = new events.EventEmitter();console.log(eventEmitter);// 注冊 kongzhi 事件 eventEmitter.on('kongzhi', function() {console.log('dddd1111'); });// 注冊 kongzhi 事件 eventEmitter.on('kongzhi', function() {console.log('dddd22222'); });// 觸發kongzhi事件 eventEmitter.emit('kongzhi');const num = eventEmitter.listeners('kongzhi'); console.log(num); // 返回監聽事件的數組 num.forEach((func) => {func(); // 我們可以這樣指定任何一個事件調用,然后會分別打印 dddd1111, dddd2222 });7. listenerCount返回注冊了指定事件的監聽數量,源碼如下:
EventEmitter.listenerCount = function(emitter, type) {if (typeof emitter.listenerCount === 'function') {return emitter.listenerCount(type);} else {return listenerCount.call(emitter, type);} };EventEmitter.prototype.listenerCount = listenerCount; function listenerCount(type) {var events = this._events;if (events !== undefined) {var evlistener = events[type];if (typeof evlistener === 'function') {return 1;} else if (evlistener !== undefined) {return evlistener.length;}}return 0; }還有其他幾個簡單的方法 once, eventNames 可以自己看下源碼了。
轉載于:https://www.cnblogs.com/tugenhua0707/p/10428807.html
總結
以上是生活随笔為你收集整理的深入理解 Node.js 中 EventEmitter源码分析(3.0.0版本)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: luoguP4242树上的毒瘤
- 下一篇: 题解 P2949 【[USACO09OP