Nodejs EventEmitter 解读

发布在 Node.js

目录

  1. events moudle
  2. Class EventEmitter
    1. EventEmitter Static Method And Property
      1. defaultMaxListeners
      2. usingDomains
      3. init()
      4. listenerCount(emitter, type)
        1. 疑问:为什么要判断原型上是否含有listenerCount方法呢?
  3. EventEmitter Property
    1. _events
    2. _eventsCount
    3. _maxListeners
  4. EventEmitter Method
    1. addListener() = on()
      1. newListener事件是在事件真正添加之前触发的
    2. emit()
      1. handler的执行
      2. 根据参数个数进行加速
    3. once()
      1. 我的疑问:这里我有一点不太理解的地方就是为什么还需要fired进行标记一下呢?
  5. removeListener(type, listener)
    1. 在事件删除之后才调用removeListener事件
  6. removeAllListeners(type)
  7. listeners(type)
  8. listenerCount(type)
  • TODO
    1. domain 部分没有进行解释
  • events moudle

    先说一下网上似乎很多人提供的使用例子来看,都是如下的

    1
    var EventEmitter = require('events').EventEmitter;

    然而在源码当中有这样的一句话

    模块定义events.js:8
    1
    2
    module.exports = EventEmitter;
    EventEmitter.EventEmitter = EventEmitter;

    所以require('events').EventEmitterrequire('events')是一样的。所以以后大家可以直接写

    1
    var EventEmitter = require('events');

    Class EventEmitter

    在源码中,构造函数极其的简单。

    constructorevent.js:5
    1
    2
    3
    function EventEmitter() {
    EventEmitter.init.call(this);
    }

    思路很简单,就是直接调用EventEmitter上的静态方法init进行构造。之后我们会介绍EventEmitter.init方法

    EventEmitter Static Method And Property

    挂在到EventEmitter上的静态属性和方法还是很多的,先说明一下静态属性:

    • defaultMaxListeners
    • usingDomains

    defaultMaxListeners

    顾名思义,默认的最大callback数量,就是如果当前对象没有指定_maxListeners,默认使用的就是这个值,默认是10
    更多请看_maxListeners

    usingDomains

    待定

    init()

    EventEmitter.initevents.js:23
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    EventEmitter.init = function() {
    this.domain = null;
    if (EventEmitter.usingDomains) {
    // if there is an active domain, then attach to it.
    domain = domain || require('domain');
    if (domain.active && !(this instanceof domain.Domain)) {
    this.domain = domain.active;
    }
    }

    if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
    this._events = {};
    this._eventsCount = 0;
    }

    this._maxListeners = this._maxListeners || undefined;
    };

    先不管domain恨死干什么用的,总之init函数给this挂上了以下四个属性:

    listenerCount(emitter, type)

    获取emitter中指定类型的callback数量

    这里有一个比较特殊的地方,就是他对对象是否含有listenerCount方法进行了判断

    listenerCountevents.js:397
    1
    2
    3
    4
    5
    6
    7
    EventEmitter.listenerCount = function(emitter, type) {
    if (typeof emitter.listenerCount === 'function') {
    return emitter.listenerCount(type);
    } else {
    return listenerCount.call(emitter, type);
    }
    };

    疑问:为什么要判断原型上是否含有listenerCount方法呢?

    EventEmitter Property

    _events

    这个属性是一个事件的缓存列表,他的key就是事件名称,value就是事件的回调函数,有如下几种取值:

    • Function
    • Array
      当只有一个回调函数的时候,就直接存储为回调函数本身,否则包装成数组进行存储

    _eventsCount

    顾名思义,就是事件的个数,这里有几个需要注意的就是,这个之有且仅在添加一个没有过的事件的时候才会加一,如果你给一个事件添加了多了callback的话,这个值并不会加一的。

    _maxListeners

    这个就是可以表示一个event的最大callback的个数。如果小于等于这个个数,不会产生任何问题。但是如果大于这个限制,将会弹出警告,说可能会导致内存泄露。
    我猜测可能是因为过多的callback会保存过多的context,从而导致内存泄露。
    敬请更正

    EventEmitter Method

    先从最常用的讲起,就是添加事件侦听喽

    addListener() = on()

    首先大家一定很熟悉on方法,大家一定想不到的是,竟然还有一个叫addListener方法。那么这两个方法有什么区别呢?
    答案就是什么区别都木有~

    而且在定义的时候,定义的是addListener而不是on,从源码中便可以看出来

    addListenerevents.js:191
    1
    2
    3
    4
    5
    6
    7
    8
    EventEmitter.prototype.addListener = function addListener(type, listener) {
    var m;
    var events;
    var existing;
    // .... 此处省略10000行
    }
    // 就是这里,大家看到了么~~
    EventEmitter.prototype.on = EventEmitter.prototype.addListener;

    所以on更多的是一种简化,而正统的却是addListener,但是虽然这个正统,相信也没有几个人来使用吧,毕竟实在是太那啥了,是吧-_-||

    好的下面言归正传,整个添加函数的流程可以看做是这样的(大家可以对照的源码看,我就不在这里把源码全部都粘贴过来了,并且我这里可能为了语句的通顺改变部分代码的执行顺序,大家请自行看出来。。。):

    1. 接受两个参数,分别是type表示事件,就是event,第二个是listener,侦听器,也就是callback
    2. 如果listener不是函数,很明显啊,抛出错误不解释
    3. 检测当前对象上是否存在_events属性,如果不存在创建之,并顺手初始化_eventsCount为0.
    4. eventsthis._events
    5. 如果events中没有typelistener,那么不解释,添加之~即events[type] = listener
    6. 如果events里面有的话,不解释,一个就数组包装一下,两个直接push
    7. 如果type类型的listener超过两个了,那么就检测一下有没有超过长度限制,具体的检测逻辑我就不在这里详细的说明了,总之就是给将检测结果挂到events[type]warned属性上了。
    8. 如果有对应的newListener事件侦听的话,就直接用typelistener.listener?listener.listener:listener触发之
    9. 返回this

    至此函数执行完毕

    那么下一个就讲一下如果触发事件吧

    newListener事件是在事件真正添加之前触发的

    emit()

    很早以前我认为emit只能emit一个参数,到现在我猜明白,想几个就几个,没人限制你。

    emit有一个很好玩的特性就是,如果你emit一个"error",如果没有事件侦听这个error的话,就会直接throw出一个error来,而其他的事件不会又这种效果,这就意味着我们最好要侦听error事件,万一出了一点错误,那将是崩溃的节奏啊~

    源码在events.js:117

    1. 检测type是否为"error"
    2. 如果是,并且没有监听到error,throw出pass进来的错误或者构建一个未捕捉的错误弹出。
    3. 如果没有对应的回调函数的话,返回false
    4. 获取构造函数并赋值给handler,这个值有可能是函数,或者是数组。
    5. 根据参数个数调用对应的函数,顺序依次运行handler
    6. 返回true

    handler的执行

    handler执行的时候,会先将当前队列复制一份,然后再进行执行。并且根据参数个数用call或者apply执行函数。放置在某一次执行期间突然掺入了其他的callback或者删除了callback,从而引发错误。

    我原本以为会使用process.nextTick进行异步的执行,后来一想不对啊,肯定要按照添加的顺序进行执行,所以依次调用。

    根据参数个数进行加速


    fastToRunevents.js:65
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // 没有参数的调用
    function emitNone(handler, isFn, self) {
    if (isFn)
    handler.call(self);
    else {
    var len = handler.length;
    var listeners = arrayClone(handler, len);
    for (var i = 0; i < len; ++i)
    listeners[i].call(self);
    }
    }
    // 一个参数
    function emitOne(handler, isFn, self) {
    // ...... 此处继续省略10000行
    }
    // 两个参数
    function emitTwo(handler, isFn, self) {
    // ...... 此处继续省略10000行
    }
    // 三个参数
    function emitThree(handler, isFn, self) {
    // ...... 此处继续省略10000行
    }
    // 不定参数
    function emitMore(handler, isFn, self) {
    // ...... 此处继续省略10000行
    }

    源码对不同的情况进行了加速,因为人们大部分情况都是使用0参数或者1参数的,所以对这几种情况进行处理是非常高效的。

    once()

    这个最简单了,就是用一个函数封装一下传入的callback,然后将这个函数,addListener进入到事件中。关于这个封装函数的写法,如下

    g()events.js:253
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var fired = false;
    function g() {
    this.removeListener(type, g);

    if (!fired) {
    fired = true;
    listener.apply(this, arguments);
    }
    }
    g.listener = listener;
    // 挂载原先的callback,用于remove的时候比较使用

    具体的就不多说了,总之就是执行后,先删除这个包装过的callback。

    我的疑问:这里我有一点不太理解的地方就是为什么还需要fired进行标记一下呢?

    removeListener(type, listener)

    删除指定类型的指定callback的事件
    很简单,分为一下几步

    1. 如果listener不是函数,抛出错误
    2. 如果events不存在或者对应的事件不存在,返回this
    3. listevents[type]
    4. 如果list就是那个callback或者list.listener是那个callback,删除
    5. 否则如果这是一个数组,那么找到对应的listener删除,否则返回this
    6. 如果上面有任意一个删除之后_eventsCount为0了,直接重新赋值this._events = {}(其实并不知道这个意义何在)
    7. 如果有任何一个删除,并且有事件侦听到了removeListener,触发之,传递typelistener
    8. 返回this

    在事件删除之后才调用removeListener事件

    removeAllListeners(type)

    根据传递的参数来决定是删除全部的还是只删除指定事件的全部

    1. 确保存在this._events,否则返回this
    2. 如果没有侦听removeListener
      • 如果没有传递type,重新赋值this._eventsthis._eventsCount
      • 如果传递了type,删除type对应的事件,根据情况重新赋值~~~
    3. 如果侦听了removeListener,除了这个本身,依次调用removeAllLiseners()进行删除,最后删除removeListener这个事件
    4. 对于每一个callback,依次调用removeListener方法进行删除
    5. 返回this

    listeners(type)

    获取指定类型的callback,否则为[]

    listenerCount(type)

    获取指定类型的callback的数量

    TODO

    domain 部分没有进行解释

    注释和共享

    • 第 1 页 共 1 页

    XGHeaven

    一个弱弱的码农


    杭州电子科技大学学生一枚


    Weifang Shandong, China