EventEmitter

最近听某个小哥说在一面中被问到发布-订阅模式的实现以及优化,听完我很懵,所以拿NODE中EventEmitter函数的源码来分析一波。

我们从EventEmitter函数内部入手,因为只要我们使用EventEmitter,最开始肯定要做的就是这一步:const event = new EventEmitter();

1
2
3
function EventEmitter() {
  EventEmitter.init.call(this);
}

EventEmitter函数怎会如此简单,只有一行代码。别太惊讶,虽然只有一行代码,但它的指向性却很明确,就是EventEmitter函数的静态属性:init函数,所以我们下一个动作就是去看EventEmitter.init函数做了什么,不过先等一等,我们还没有解决这一行代码里的this指向问题,上面我们说过把EventEmitter函数当做构造函数,使用new操作符生成一个实例对象,所以很显然,此处的this即为新生成的实例对象。那么call方法呢,实际上,它的作用是将EventEmitter.init函数内的this都指向这个实例对象。

我们已经把这一行代码分析得很透彻了,我们再在心里默念一遍: init函数是EventEmitter函数的静态属性,init函数里的所有this都指向实例对象。OK!可以看init函数的代码了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
EventEmitter.init = function() {

  if (this._events === undefined ||
      this._events === Object.getPrototypeOf(this)._events) {
    this._events = Object.create(null);
    this._eventsCount = 0;
  }

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

可以看到,init函数内部首先判断实例对象上是否存在_events属性,如果不存在,利用Object.create创建一个空对象来初始化_events属性,然后给实例对象添加_eventsCount属性,初始值为0。我们依次来解释这两个属性(events属性和_eventCount属性),①_events属性值是一个对象,它很重要,所有的事件最后都会挂载它的身上,形如:

1
2
3
4
5
6
7
{
	events: {
		eventType1: function() {},
		eventType2: [function(){}, function(){}],
		eventType3: function() {}
	}
}

②_eventCount属性用来记录我们监听的事件类型种类数量,这么说似乎很抽象,举个例子:

1
2
3
4
const event = new EventEmitter();
event.on('eventType1', () => { console.log('我是eventType1的第一个回调函数'); });
event.on('eventType1', () => { console.log('我是eventType1的第二个回调函数'); });
event.on('eventType2', () => { console.log('我是eventType2的第一个回调函数'); });

此时实例对象的_eventCount属性值为2,而不是3。

回到init函数继续看下面的代码,只剩一行代码,这行代码给实例对象添加_maxListeners属性,最开始为undefined。_maxListeners属性的含义是,监听某个事件类型时,最多可以添加多少个回调函数。但请注意并不是强制限制,当某个事件类型对应的回调函数个数超出_maxListeners属性值时,只会在控制台warning提醒。举个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var emitter = new EventEmitter(); 
//setMaxListeners函数的作用就是设置实例对象的_maxListeners属性
// 后面会详细介绍该方法
emitter.setMaxListeners(1);

emitter.on('eventType', () => console.log('我是eventType的第一个回调函数'))
emitter.on('eventType', () => console.log('我是eventType的第二个回调函数'))


// MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 2 eventType listeners added to [EventEmitter]. Use emitter.setMaxListeners() to increase limit

不要将_maxListeners属性与_eventsCount属性弄混!两者还是有很大区别的,_eventsCount属性是记录事件类型种类个数,而_maxListeners属性是某个事件类型可以添加的最大回调函数个数。

此刻,init函数已经分析完毕,我们再来回忆一下init函数做了什么: 初始化实例对象的_events、_eventsCount、_maxListeners属性。 其实也不是很难,是吧?好了,按照正常的顺序我们现在应该看看监听方法: on/addListener 或者 移除监听方法: off/removeListener了,但考虑刚才提到了_maxListeners属性,我觉得还是先把与_maxListeners属性相关的内容先都介绍完,再看其他。

我们在init函数内也注意到了,最开始的_maxListeners属性值为undefined,也许你会产生疑问,不应该有一个默认值吗?其实是有的,默认值存在于EventEmitter函数的静态属性: defaultMaxListeners上。所以懂了吧,实例对象上的_maxListeners属性只是给我们提供了一个定制化的选择,如果不设置,则某个事件类型可以设置的最大回调函数个数则由静态属性: defaultMaxListeners控制。那我此刻就想定制化呢?是可以的:

1
2
3
4
5
6
7
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
  if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {
    throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.');
  }
  this._maxListeners = n;
  return this;
};

存在于EventEmitter.prototype上的setMaxListeners方法给我们提供了定制化_maxListeners属性的能力,代码也很直白地展示了这个方法的意义,先检测参数n,如果参数n不是大于0 的数,则抛出一个错误,反之将参数n赋给实例对象的_maxListeners属性。

看到此处还清醒吗?我觉得如果看到现在的人一定会产生一个疑问,定制化完以后,是在哪里做了选择,选择是使用实例对象的_maxListeners属性还是使用EventEmitter.defaultMaxListeners。其实是在获取最大回调函数个数的方法里,也就是EventEmitter.prototype.getMaxListeners方法:

1
2
3
4
5
6
7
8
9
function _getMaxListeners(that) {
  if (that._maxListeners === undefined) {
    return EventEmitter.defaultMaxListeners;
  }
  return that._maxListeners;
}
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
  return _getMaxListeners(this);
};

在_getMaxListeners函数里,如果用户设置了_maxListeners属性,则优先使用实例对象的_maxListeners属性,否则使用EventEmitter函数的静态属性: defaultMaxListeners。代码很简单,解释这个程度我觉得可以了。

现在我们已经知道了实例对象的_maxListeners属性的作用以及如何设置它/如何获取它,可以结束这一个part了吗?不!EventEmitter函数的静态属性: defaultMaxListenrs呢?我们还没有介绍它,它的默认值其实是10,是在哪里做的操作?它有什么特点?赶紧上代码,我写的有点困了...:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var defaultMaxListeners = 10;

Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
  enumerable: true,
  get: function() {
    return defaultMaxListeners;
  },
  set: function(arg) {
    if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
      throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.');
    }
    defaultMaxListeners = arg;
  }
});

基础好的一眼锁定defaultMaxListeners属性是访问器属性,在defaultMaxListeners属性的get/set函数里都引用了一个本地变量: defaultMaxListeners,其值为10。当我们访问EventEmitter.defaultMaxListeners时,返回本地变量: defaultMaxListeners,当我们设置EventEmitter.defaultMaxListeners时,将参数arg赋给本地变量: defaultMaxListeners。其实,这就是闭包,由此可见,闭包真的无处不在。

此时,我们暂缓,总结一下已经分析了哪些内容。

①EventEmitter的静态属性: init、defaultMaxListeners。

②EventEmitter.prototype上的属性: getMaxListeners、setMaxListeners。

③实例对象拥有_events、_eventCount、_maxListners属性。

如果你脑海中对上面三条没有清晰深刻的认识,求你了,再从头读一遍。接下来我们要介绍第一个重要方法: EventEmitter.prototype.addListener,它有别名,是EventEmitter.prototype.on:

1
EventEmitter.prototype.on = EventEmitter.prototype.addListener;

我们来看一下EventEmitter.prototype.addListener的源码:

 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
function _addListener(target, type, listener, prepend) {
  var m;
  var events;
  var existing;

  // 检查listener是不是函数
  checkListener(listener);

  // 获取实例的_event属性
  events = target._events;

  // 连实例的_events属性都没有???那么新建一个_events属性,并且赋值给events变量。
  if (events === undefined) {
    events = target._events = Object.create(null);
    target._eventsCount = 0;
  } else {
    // To avoid recursion in the case that type === "newListener"! Before
    // adding it to the listeners, first emit "newListener".
    // 如果注册了「newListener」类型事件,那么events.newListener肯定不为空
    // 「newListener」存在的意义是,当我们监听某类型的事件之前,我们可以做些什么
    if (events.newListener !== undefined) {
      target.emit('newListener', type,
                  listener.listener ? listener.listener : listener);
    // 在target.emit('newListener'时,很可能再监听一些事件,所以重新给events变量赋值
      // Re-assign `events` because a newListener handler could have caused the
      // this._events to be assigned to a new object
      events = target._events;
    }
    // 获取某个type对应的回调函数/回调函数数组
    existing = events[type];
  }

  if (existing === undefined) {
    // 如果existing不存在,说明此前没有注册过,则events[type]、existing都为listener函数
    // Optimize the case of one listener. Don't need the extra array object.
    existing = events[type] = listener;
    // 多监听了一个事件类型,则给实例的_eventsCount属性加一个1。
    ++target._eventsCount;
  } else {
    // 如果typeof existing === 'function'说明监听过此事件类型,但只监听了一个回调函数,
    if (typeof existing === 'function') {
      // Adding the second element, need to change to array.
      existing = events[type] =
        prepend ? [listener, existing] : [existing, listener];
      // If we've already got an array, just append.
    } else if (prepend) {
      existing.unshift(listener);
    } else {
      existing.push(listener);
    }

    // Check for listener leak
    m = _getMaxListeners(target);
    if (m > 0 && existing.length > m && !existing.warned) {
      existing.warned = true;
      // No error code for this since it is a Warning
      // eslint-disable-next-line no-restricted-syntax
      var w = new Error('Possible EventEmitter memory leak detected. ' +
                          existing.length + ' ' + String(type) + ' listeners ' +
                          'added. Use emitter.setMaxListeners() to ' +
                          'increase limit');
      w.name = 'MaxListenersExceededWarning';
      w.emitter = target;
      w.type = type;
      w.count = existing.length;
      ProcessEmitWarning(w);
    }
  }

  return target;
}

EventEmitter.prototype.addListener = function addListener(type, listener) {
  return _addListener(this, type, listener, false);
};

先来看参数,EventEmitter.prototype.addListener函数接受两个参数: type、listener。type为监听的事件类型,listener为对应的回调函数。而函数内部则是调用了本地函数: _addListener且分别传入this、 type、 listener、 false。type和listener我们已经说过,剩下this和false。this指向实例对象,因为我们是event.addListener('eventType',() => {})这样来用的。而false,代表是否要把listener函数插到回调函数数组的开头,默认为false,也就是push到数组的尾部。

接下来我们来看本地函数: _addListener做了什么,首先调用了checkListener函数,checkListener函数的作用是判断listener参数是否是函数,如果不是函数则抛出一个错误。然后取出了实例对象的_events属性,赋给函数内的本地变量: events。紧接着对本地变量:events做了判断,如果不存在(等于undefined): 则再重新初始化一遍,也就是重新了EventEmitter.init的行为,如果events变量存在: 先查看events变量上是否存在newListener属性,如果存在newListener属性,就触发事件类型: newListener事件,并将type,listener传入。 此处我第一次看内心是崩溃的,为什么要检查newListener属性,newListener又是个啥?别着急,首先我们要明确两个事情:

①我们监听了什么事件类型,这个类型就会成为_events的一个属性,比如说我们监听了getup事件类型,那么_events.getup则会存在,且属性值是对应的回调函数/回调函数数组。

②第一条如果明确,那么我们就知道检查newListener属性目的是想知道是否之前监听过newListener事件。

③newListener事件类型存在意义是,在在我们监听某个事件类型前,我们可以做一些事情,相当于给我们开了一个口子。

翻篇!继续看代码,然后从本地变量: events中取出type对应的回调函数/回调函数数组赋给本地变量: existing。

updatedupdated2020-07-312020-07-31