Nodejs EventEmitter 解读

发布在 Node.js

目录

  1. events module
  2. Class EventEmitter
    1. EventEmitter Static Method And Property
      1. defaultMaxListeners
      2. usingDomains
      3. init()
      4. listenerCount(emitter, type)
      5. 疑问:为什么要判断原型上是否含有listenerCount方法呢?
    2. EventEmitter Property
      1. _events
      2. _eventsCount
      3. _maxListeners
    3. EventEmitter Method
      1. addListener() = on()
      2. newListener事件是在事件真正添加之前触发的
      3. emit()
      4. handler的执行
      5. 根据参数个数进行加速
      6. once()
      7. removeListener(type, listener)
      8. removeAllListeners(type)
      9. listeners(type)
      10. listenerCount(type)
  3. TODO
    1. domain 部分没有进行解释

events module

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

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

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

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

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

1
var EventEmitter = require('events');

Class EventEmitter

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

constructorevents.js
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
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
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
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
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
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