事件的編程方式具備輕量級、鬆耦合、只關注事務點等優點,在瀏覽器端,有着本身的一套DOM
事件機制,其中含包括這諸如事件冒泡,事件捕獲等;然而Node
的事件機制沒有事件冒泡等,其原理就是設計模式中的觀察者模式。Node
不少的模塊繼承這個事件模塊,下面咱們就來根據源碼來學習下其API
,作到知其然更知其因此然。node
const EventEmitter = require("events"); const EventEmitter = require("events").EventEmitter;
常常會看到這種兩種方式來引入咱們的events
模塊,可是在Node
的高版本中能夠直接使用第一種方式,高版本也支持下面這種方式,下面的這種方式主是在Node
的0.10.x
版本時使用,源碼中也是很清楚,之因此這麼作就是爲了兼容低版本時寫下的Node
代碼:git
module.exports = EventEmitter; EventEmitter.EventEmitter = EventEmitter;
注:以後提到的obj
爲EventEmitter
的實例對象,也就是obj = new EventEmitter()
github
獲得咱們的事件構造函數後,咱們就能夠來實例化一個事件對象:編程
const EventEmitter = require("events"), follow = new EventEmitter(); follow.on("node", question => { console.log(`有一個關於node的問題: ${question}`); }); follow.emit("node", "jade與ejs選擇哪一個?");
這是一個簡單的使用,下面咱們就來看看咱們所用到的API
以及它們的實現:設計模式
on(type, listener)
來訂閱事件,傳入的type
參數爲事件名,listener
爲待發布函數。同時addListener
方法和on
方法有着一樣的效果,指向的是內存的同一塊:數組
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
在調用on
時,倘若咱們訂閱了newListener
事件,該事件會先被髮布。瀏覽器
那麼問題來了?訂閱的事件被存儲在了哪裏呢?app
答案就是obj._events
,這是一個事件集合,事件名就是該集合的鍵名,當事件的待補發函數只有一個時,鍵值爲函數;當有多個時,鍵值爲數組。爲何把obj._events
叫作集合而不能稱爲嚴格意義上的對象,來看這個集合的構造函數:函數
function EventHandlers () {} EventHandlers.prototype = Object.create(null);
能夠見得,EventHandlers.prototype
是與Object.prototype
處於同一層級的而非是繼承自Object.prototype
,因此說由EventHandlers
實例出來的對象更加的"純淨",並無諸如toString
等方法,更像是一個集合。學習
隨着給一個事件添加待發布函數,當添加的數量超過10條是,會發現有警告:
(node) warning: possible EventEmitter memory leak detected. 11 git listeners added. Use emitter.setMaxListeners() to increase limit.
產生警告的緣由就是事件待發布函數數組的長度超過了默認的最大容量,默認的最大容量是EventEmitter.defaultMaxListeners
,而這個屬性是一個getter/setter
訪問器,訪問的是變量defaultMaxListeners
的值,也就是10。
// 獲得最大容量 function $getMaxListeners (that) { if (that._maxListeners === undefined) return EventEmitter.defaultMaxListeners; return that._maxListeners; } // 發出警告代碼: if (!existing.warned) { m = $getMaxListeners(target); if (m && m > 0 && existing.length > m) { existing.warned = true; process.emitWarning('Possible EventEmitter memory leak detected. ' + `${existing.length} ${type} listeners added. ` + 'Use emitter.setMaxListeners() to increase limit'); } }
觀察得到最大容量函數能夠發現,給obj._maxListeners
賦值能夠提高咱們的最大容量(obj._maxListeners
初始化時被賦值爲undefined
),能夠利用setMaxListeners(n)
方法來進行賦值:
EventEmitter.prototype.setMaxListeners = function (n) { if (typeof n !== "number" || n < 0 || isNaN(n)) throw new TypeError("n must be a position number"); this._maxListeners = n; return this; };
看源碼能夠發現,訂閱事件實際上是用的_addListener
函數,其最後一個參數爲prepend
,表明着是否將待發布函數添加到事件數組的第一個,因此應該還有一個prependListener(type, listener)
函數,能夠將listener
添加到obj.events.type
的第一個位置。
once(type, listener)
,經過這種方式添加的待發布函數,只能被髮布一次,發佈一次後就會被移除。
// 將listener包裹在一個g函數中,在每次執行時,現將該函數從事件數組中移除 // 真正的待發布函數成爲了g函數的屬性 function _onceWrap (target, type, listener) { var fired = false; function g () { target.removeListener(type, g); // 先移除 if (!fired) { fired = true; listener.apply(target, arguments); // 再發布 } } g.listener = listener; return g; }
於此對應的還有prependOnceListener
方法,下面來看一個例子:
work.once("git", pull); work.on("git", () => { console.log("git status"); }); work.emit("git"); console.log("第二次"); work.emit("git"); // git pull // git status // 第二次 // git status
emit(type, args)
來進行事件的發佈,在實現上也很簡單就是執行obj.events.type
的函數或者遍歷obj.events.type
數組一次執行函數,須要注意的是error
事件的發佈,若是沒有訂閱error
事件的話,發佈時,就會用到throw new Error()
。
removeListener(type, listener)
來移除${type}
事件的listener
函數
removeAllListeners(type)
,當傳入type
時會將type
事件所有移除;不傳入參數時,會將obj._events
重置。
在移除時,倘若給obj
訂閱了removeListener
事件的話,那麼在每移除一個待發布函數時,會發布一次該事件,在將obj
重置時,也會最後將該事件移除。
function pull () { console.log("git pull"); }; work.on("removeListener", (type, fn) => { console.log(`remove ${type} ${fn.name} event`); }) work.on("git", pull); work.on("git", () => { console.log("git status"); }); work.removeListener("git", pull); work.emit("git"); // 依次輸出 // remove git pull event // git status
eventNames
,返回實例對象全部的事件名數組或者一個空數組,源碼中利用了ES6
的新方法Reflect.ownKeys
來得到obj._events
對象的自身屬性:
EventEmitter.prototype.eventNames = function eventNames() { return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : []; };
listenerCount(type)
返回事件的待發布函數的的數量,也就是obj._events.length or 1
,這個方法在obj
上和EventEmitter
都有,本質上都是調用下面這個方法,實現也是很明瞭:
function listenerCount (type) { const events = this._events; if (events) { const evlistener = events[type]; if (typeof evlistener === "function") { return 1; } else if (evlistener) { return evlistener.length; } } }
listeners(type)
,返回${type}
事件的待發布函數數組或者空數組,須要注意是這個數組並非obj.events.type
的引用。
此次閱讀Node的源代碼發現,Node源碼中對於原生的slice
和splice
並無使用,而是本身寫了一個針對性更增強的arrayClone
和spliceOne
函數,不知這樣寫的緣由是否是要將速度提高,由於看V8源碼會發現,slice
和splice
的實現有一些複雜,都有額外的判斷來對參數進行規範化,像Node源碼本身寫的話,減小了這些無用的判斷,從而提高了效率。固然這只是我我的的一些理解,若有錯誤還請你們指出。