Node 之 Event 模块

写在前面

事宜的编程体式格局具有轻量级、松耦合、只关注事件点等上风,在浏览器端,有着本身的一套DOM事宜机制,个中含包含这诸如事宜冒泡,事宜捕捉等;但是Node的事宜机制没有事宜冒泡等,其道理就是设想形式中的视察者形式Node许多的模块继续这个事宜模块,下面我们就来依据源码来进修下其API,做到知其然更知其所以然。

引入模块

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

常常会看到这类两种体式格局来引入我们的events模块,但是在Node的高版本中能够直接运用第一种体式格局,高版本也支撑下面这类体式格局,下面的这类体式格局主是在Node0.10.x版本时运用,源码中也是很清晰,之所以这么做就是为了兼容低版本时写下的Node代码:

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

注:以后提到的objEventEmitter的实例对象,也就是obj = new EventEmitter()

基础运用

取得我们的事宜组织函数后,我们就能够来实例化一个事宜对象:

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事宜,该事宜会先被宣布。

那末题目来了?定阅的事宜被存储在了那里呢?

答案就是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

    原文作者:zp1996
    原文地址: https://segmentfault.com/a/1190000006582426
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞