Node 之 Event 模塊

寫在前面

事件的編程方式具備輕量級、鬆耦合、只關注事務點等優點,在瀏覽器端,有着本身的一套DOM事件機制,其中含包括這諸如事件冒泡,事件捕獲等;然而Node的事件機制沒有事件冒泡等,其原理就是設計模式中的觀察者模式Node不少的模塊繼承這個事件模塊,下面咱們就來根據源碼來學習下其API,作到知其然更知其因此然。node

引入模塊

const EventEmitter = require("events");
const EventEmitter = require("events").EventEmitter;

常常會看到這種兩種方式來引入咱們的events模塊,可是在Node的高版本中能夠直接使用第一種方式,高版本也支持下面這種方式,下面的這種方式主是在Node0.10.x版本時使用,源碼中也是很清楚,之因此這麼作就是爲了兼容低版本時寫下的Node代碼:git

module.exports = EventEmitter;
EventEmitter.EventEmitter = EventEmitter;

注:以後提到的objEventEmitter的實例對象,也就是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

其他API

  • 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源碼中對於原生的slicesplice並無使用,而是本身寫了一個針對性更增強的arrayClonespliceOne函數,不知這樣寫的緣由是否是要將速度提高,由於看V8源碼會發現,slicesplice的實現有一些複雜,都有額外的判斷來對參數進行規範化,像Node源碼本身寫的話,減小了這些無用的判斷,從而提高了效率。固然這只是我我的的一些理解,若有錯誤還請你們指出。

附:解析的events.js

相關文章
相關標籤/搜索