深入理解 Node.js 中 EventEmitter源码分析(3.0.0版本)

events模块对外提供了一个 EventEmitter 对象,即:events.EventEmitter. EventEmitter 是NodeJS的核心模块events中的类,用于对NodeJS中的事件进行统一管理,使用events可以对特定的API事件进行添加,触发和移除等。
我们可以通过 require(‘events‘)来访问该模块。

比如如下代码:

// 引入 events 模块
const events = require(‘events‘);

console.log(events);

// 创建 eventEmitter 对象
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

我们先把如上代码放入 main.js 里面,然后在 项目中对应目录下 执行 node main.js 执行结果如下:

如上图可以看到,console.log(events); 打印后,有 EventEmitter 属性,defaultMaxListeners 属性(getter/setter) 方法,init函数属性,及 listenerCount 函数属性等。

1. defaultMaxListeners的含义是:默认事件最大监听个数,在源码中 默认是10个,设置最大监听个数为10个,因为如果监听的个数过多的话,会导致 内存泄露的问题产生。因此默认设置了十个。当然我们在源码中可以设置或者获取最大的监听个数,我们可以设置最大监听的个数 如下代码:

// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
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;
};

当然有设置监听的个数,我们也有获取最大的监听个数,如下源码:

function $getMaxListeners(that) {
  if (that._maxListeners === undefined)
    return EventEmitter.defaultMaxListeners;
  return that._maxListeners;
}

EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
  return $getMaxListeners(this);
};

当然源码中也使用了 Object.defineProperty方法监听该属性值是否发生改变,如下基本源码:

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值发生改变的话,就会调用相对应的getter/setter方法。

2. events 中的init函数中基本源码如下:

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;
};

如上基本代码:this._events 的含义是 保存所有的事件对象,事件的触发和事件的移除操作监听等都在
这个对象_events的基础上实现。

this._eventsCount 的含义是:用于统计事件的个数,也就是_events对象有多少个属性。

this._maxListeners 的含义是:保存最大的监听数的含义。

3. event中的listenerCount函数的作用是:返回指定事件的监听器数量。
如下基本代码:

EventEmitter.listenerCount = function(emitter, type) {
  if (typeof emitter.listenerCount === ‘function‘) {
    return emitter.listenerCount(type);
  } else {
    return listenerCount.call(emitter, type);
  }
};

EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
  var events = this._events;

  if (events !== undefined) {
    var evlistener = events[type];

    if (typeof evlistener === ‘function‘) {
      return 1;
    } else if (evlistener !== undefined) {
      return evlistener.length;
    }
  }

  return 0;
}

4. events中的EventEmitter属性

该属性就是events对外提供的一个对象类,使用 EventEmitter 类就是对事件的触发和监听进行封装。

现在我们看下创建 eventEmitter 对象,如下代码吧:

// 创建 eventEmitter 对象
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

打印信息如下:

可以看到该实列有 _events属性及_maxListeners属性,及该实列上的原型有很多对应的方法,比如 addListener, emit,listenerCount, listeners, on, once, removeAllListeners, removeListener, setMaxListeners 等方法,及_events 及 _maxListeners 等属性。我们先来看下一些简单实列上的属性的基本源码如下:

// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;

EventEmitter.prototype._events = undefined;
EventEmitter.prototype._eventsCount = 0;
EventEmitter.prototype._maxListeners = undefined;

一:EventEmitter基本的API使用:

1. addListener(event, listener) 为指定的事件注册一个监听器。该方法是on的别名。该方法接收一个事件名和一个回调函数。
如下代码演示:

// 引入 events 模块
const events = require(‘events‘);

console.log(events);

// 创建 eventEmitter 对象
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

// 注册 kongzhi 事件
eventEmitter.on(‘kongzhi‘, function() {
  console.log(‘dddd‘); // 打印 dddd
});

// 触发kongzhi事件
eventEmitter.emit(‘kongzhi‘);

2. listenerCount(eventName)

该方法返回注册了指定事件的监听数量。基本语法如下:

EventEmitter.listenerCount(eventName);

eventName: 指监听的事件名

如下基本代码:

// 引入 events 模块
const events = require(‘events‘);

console.log(events);

// 创建 eventEmitter 对象
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

// 注册 kongzhi 事件
eventEmitter.on(‘kongzhi‘, function() {
  console.log(‘dddd1111‘);
});

// 注册 kongzhi 事件
eventEmitter.on(‘kongzhi‘, function() {
  console.log(‘dddd22222‘);
});

// 触发kongzhi事件
eventEmitter.emit(‘kongzhi‘);

const num = eventEmitter.listenerCount(‘kongzhi‘);
console.log(num); // 返回2 说明注册了两个 kongzhi 这个事件

比如源码中如下代码所示:

EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
  var events = this._events;

  if (events !== undefined) {
    var evlistener = events[type];

    if (typeof evlistener === ‘function‘) {
      return 1;
    } else if (evlistener !== undefined) {
      return evlistener.length;
    }
  }
  return 0;
}

如上代码,先判断 this._events 是否保存了事件,如果保存了事件的话,如果它是个函数的话,那么 return 1; 否则的话,如果不等于undefined,直接返回该监听事件名的长度。其他的情况下 返回 0;

因此上面我们通过如下代码,就可以获取到该监听的事件的长度了:如下代码:

const num = eventEmitter.listenerCount(‘kongzhi‘);
console.log(num); // 返回2 说明注册了两个 kongzhi 这个事件

3. listeners(event)

该方法返回指定事件的监听器数组。

如下代码演示:

// 引入 events 模块
const events = require(‘events‘);

console.log(events);

// 创建 eventEmitter 对象
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

// 注册 kongzhi 事件
eventEmitter.on(‘kongzhi‘, function() {
  console.log(‘dddd1111‘);
});

// 注册 kongzhi 事件
eventEmitter.on(‘kongzhi‘, function() {
  console.log(‘dddd22222‘);
});

// 触发kongzhi事件
eventEmitter.emit(‘kongzhi‘);

const num = eventEmitter.listeners(‘kongzhi‘);
console.log(num); // 返回监听事件的数组

num.forEach((func) => {
  func(); // 我们可以这样指定任何一个事件调用,然后会分别打印 dddd1111, dddd2222
});

4. once(event, listener)
该函数的含义是:为指定事件注册一个单次监听器,也就是说监听器最多只会触发一次,触发后立刻解除该监听器。
如下代码:

// 引入 events 模块
const events = require(‘events‘);

console.log(events);

// 创建 eventEmitter 对象
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

// 使用on注册监听器
eventEmitter.on(‘kongzhi‘, function() {
  console.log(‘我on事件触发了多少次呢?‘);
});

// 注册 kongzhi 事件
eventEmitter.once(‘kongzhi‘, function() {
  console.log(‘我once事件触发了多少次呢?‘);
});

// 触发kongzhi事件
eventEmitter.emit(‘kongzhi‘);
eventEmitter.emit(‘kongzhi‘);

如上代码所示:使用on 事件注册的话,如果使用emit触发的话,触发了多少次,就执行多少次,使用once注册事件的话,emit触发多次的话,最后也只能调用一次,如下执行结果如下:

5. removeListener(eventName, listener)

该方法的作用是:移除指定事件的某个监听器。监听器必须是该事件已经注册过的监听器。

eventName: 事件名称
listener: 回调函数的名称

如下代码演示:

// 引入 events 模块
const events = require(‘events‘);

console.log(events);

// 创建 eventEmitter 对象
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

const callback = function() {
  console.log(11111);
};
// 使用on注册监听器
eventEmitter.on(‘kongzhi‘, callback);

// 触发事件 kongzhi
eventEmitter.emit(‘kongzhi‘);

eventEmitter.removeListener(‘kongzhi‘, callback);

/*
 我们继续触发事件 kongzhi 是不会触发的,因为上面已经使用
 removeListener 已经删除了 kongzhi 事件了
 */
eventEmitter.emit(‘kongzhi‘);

6. removeAllListeners([event])

移除所有事件的所有监听器,如果我们指定事件的话,则是移除指定事件的所有监听器。

参数event: 该参数的含义是事件名称,如果指定了该事件名称的话,则会删除该指定的事件名称对应的所有函数,如果没有指定任何事件名的话,则是删除所有的事件。如下代码所示:

// 引入 events 模块
const events = require(‘events‘);

console.log(events);

// 创建 eventEmitter 对象
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

const callback = function() {
  console.log(11111);
};
// 使用on注册监听器
eventEmitter.on(‘kongzhi‘, callback);

// 注册kongzhi2事件
eventEmitter.on(‘kongzhi2‘, callback);

// 触发事件 kongzhi 是可以触发的
eventEmitter.emit(‘kongzhi‘);

// 删除所有的监听器

eventEmitter.removeAllListeners();

/*
 我们继续触发事件 kongzhi 和 kongzhi2 是不会触发的,因为上面已经使用
 removeAllListeners 已经删除了 所有的 事件了
 */
eventEmitter.emit(‘kongzhi‘);

eventEmitter.emit(‘kongzhi2‘);

7. setMaxListeners(n)

该方法的作用是 设置监听器的默认数量。因为EventEmitters默认的监听器为10个,如果超过10个就会输出警告信息,因此使用该方法,可以设置监听器的默认数量, 使之最大的数量不会报错。

如下代码所示:

// 引入 events 模块
const events = require(‘events‘);

console.log(events);

// 创建 eventEmitter 对象
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

const callback = function() {
  console.log(11111);
};

// 设置默认监听数量最多为1个,如果超过该数量,控制台会发出警告
eventEmitter.setMaxListeners(1);

// 使用on注册监听器
eventEmitter.on(‘kongzhi‘, callback);

// 注册kongzhi事件
eventEmitter.on(‘kongzhi‘, callback);

// 触发事件 kongzhi
eventEmitter.emit(‘kongzhi‘);

执行结果如下所示:

当我们把上面的eventEmitter.setMaxListeners(2); 设置为大于1的时候,就不会报错了,比如最大设置的默认数量为2,监听器最大为2,就不会报错了。

如下图所示:

二:EventEmitter 源码分析

EventEmitter类它实质是一个观察者模式的实现,什么是观察者模式呢?观察者模式定义了对象间的一种一对多的关系,它可以让多个观察者对象同时监听一个主题对象,当一个主题对象发生改变时,所有依赖于它的对象都会得到一个通知。
那么在观察者模式中最典型的实列demo就是 EventEmitter类中on和emit方法,我们可以通过on注册多个事件进行监听,而我们可以通过emit方法来对on事件进行触发。比如如下demo代码:

// 引入 events 模块
const events = require(‘events‘);

console.log(events);

// 创建 eventEmitter 对象
const eventEmitter = new events.EventEmitter();

eventEmitter.on(‘kongzhi‘, function(name) {
  console.log(‘hello‘, name); // 输出:hello 我叫空智
});

eventEmitter.emit(‘kongzhi‘, ‘我叫空智‘);

如上代码,我们通过eventEmitter的emit方法,发出 kongzhi 事件,然后我们通过 eventEmitter 的on方法进行监听,从而执行相对应的函数,从而打印出信息出来。其中on方法属于多个观察者那个对象,它可以有多个on方法进行监听 emit中触发的那个主题对象,当那个emit触发的主体对象发生改变时,所有的on方法监听的对象都会触发。

上面我们知道了 EventEmitter模块的on和emit的用途后,我们首先来实现一个包含 emit和on方法的EventEmitter类。
如下代码:

class EventEmitter {
  constructor() {
    this._events = {}; // 保存所有的事件
  }
  on(eventName, callback) {
    if (!this._events[eventName]) {
      this._events[eventName] = [];
    }
    this._events[eventName].push(callback);
  }
  emit(eventName, ...arg) {
    // 如果on方法监听了多个事件的话,依次执行代码
    if (this._events[eventName]) {
      for (let i = 0; i < this._events[eventName].length; i++) {
        this._events[eventName][i](...arg);
      }
    }
  }
}

// 下面是实例化上面的类函数, 然后会依次执行

const eventEmitter = new EventEmitter();

eventEmitter.on(‘kongzhi‘, function(age) {
  console.log(‘我是空智,我今年‘ + age + ‘岁‘); // 会打印:我是空智,我今年30岁
});

eventEmitter.on(‘kongzhi‘, function(age) {
  console.log(‘我是空智2,我今年‘ + age + ‘岁‘); // 会打印:我是空智2,我今年30岁
});

eventEmitter.emit(‘kongzhi‘, 30);

如上代码就实现了一个类为 EventEmitter 中的on和emit方法了,on是注册事件,emit是触发该事件,然后执行的对应的回调函数,首先通过on去注册事件,然后我们会通过 this._event对象来保存该事件的回调函数,this._event中对象的key就是on注册的事件名,然后on注册的回调函数就是 this._event中使用数组保存起来,该this._event的值就是一个数组函数,然后在emit方法中,使用for循环进行依次执行该回调函数。就会依次触发对应的函数了。
emit(eventName, ...arg) 方法传入的参数,第一个为事件名,其他参数事件对应执行函数中的实参。该方法的作用是:从事件对象中,寻找对应的key为eventName的属性,执行该属性所对应的数组里面的每一个函数。

上面是一个简单的实现,下面我们再来看看 events.js 源码中是如何实现的呢?

events.js 源码分析:

function EventEmitter() {
  EventEmitter.init.call(this);
}
module.exports = EventEmitter;

EventEmitter.EventEmitter = EventEmitter;

EventEmitter.prototype._events = undefined;
EventEmitter.prototype._eventsCount = 0;
EventEmitter.prototype._maxListeners = undefined;

EventEmitter.init = function() {
  /*
   如果this._event为undefined,或 Object.getPrototypeOf 上面也没有 events的话(es5),
   则使用Object.create()创建一个空的对象. 并且设置_eventsCount事件个数为0,也就是初始化。
   并且初始化 _maxListeners,默认为10个,未初始化之前是undefined。
  */
  if (this._events === undefined ||
      this._events === Object.getPrototypeOf(this)._events) {
    this._events = Object.create(null);
    this._eventsCount = 0;
  }

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

如上代码,构造函数 EventEmitter 会调用 EventEmitter.init方法进行初始化,然后使用 this._events = Object.create(null);创建一个对象保存到 this._events中,作用是用于存储和统一管理所有类型的事件,在创建构造函数的时候导出了 EventEmitter,后面所有的方法放在该对象中的原型中。_maxListeners的含义我们上面已经讲解过,是保存最大的监听数。默认为10个。

当然我们可以在源码中使用 getMaxListeners/setMaxListeners方法设置最大的监听数,比如如下的源码:

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;
};

function $getMaxListeners(that) {
  if (that._maxListeners === undefined)
    return EventEmitter.defaultMaxListeners;
  return that._maxListeners;
}

EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
  return $getMaxListeners(this);
};

在源码中,我们也对 最大的监听数使用了 Object.defineProperty方法进行监听,如下源码所示:

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;
  }
});

2. addEventListener 或 on 添加事件源码如下:

/*
 addListener函数的别名是on,该函数有两个参数,type是事件名称,listener是监听函数。
 之后会调用 _addListener函数进行初始化,从_addListener函数中返回的是this,这样的设计目的是可以进行链式
 调用。
*/
EventEmitter.prototype.addListener = function addListener(type, listener) {
  return _addListener(this, type, listener, false);
};

EventEmitter.prototype.on = EventEmitter.prototype.addListener;
/*
 该_addListener函数有四个参数,分别为 target指向了当前this对象。
 type为事件名称,listener为监听事件的函数名。
 prepend参数如果为true的话,是把监听函数插入到数组的头部,默认为false,插入到数组的尾部。
*/
function _addListener(target, type, listener, prepend) {
  var m;
  var events;
  var existing;
  /*
   如果使用on或addEventListener注册事件时,如果listener参数不是一个函数的话,会抛出错误。如下代码。
  */
  if (typeof listener !== ‘function‘) {
    throw new TypeError(‘The "listener" argument must be of type Function. Received type ‘ + typeof listener);
  }
  /*
   1. 拿到当前的所有的事件 _events, 即拿到一个事件对象,对象中存放了触发事件组或空对象。
      如果this._events是undefined的话,就使用 Object.create(null) 创建一个
      空对象给events保存起来。然后设置 this._eventsCount = 0;  用于统计事件的个数,也就是_events对象有多少个属性。
   2. 如果有this._event的话,如果使用了on或addEventListener注册了 newListener 事件的话,就直接触发执行它。
  */
  events = target._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".
    if (events.newListener !== undefined) {
      target.emit(‘newListener‘, type,
                  listener.listener ? listener.listener : listener);

      // Re-assign `events` because a newListener handler could have caused the
      // this._events to be assigned to a new object
      /*
       重新注册events,因为 newListener钩子可能导致this._event去重新注册个新对象。
      */
      events = target._events;
    }
    // 保存上一次触发的事件
    existing = events[type];
  }
  /*
   exiting变量是保存上一次触发的事件对象,比如:如下测试代码:
    // 引入 events 模块
    const events = require(‘events‘);
    console.log(events);
    // 创建 eventEmitter 对象
    const eventEmitter = new events.EventEmitter();
    console.log(eventEmitter);

    const callback = function() {
      console.log(11111);
    };
    // 使用on注册监听器
    eventEmitter.on(‘kongzhi‘, callback);

    // 注册kongzhi2事件
    eventEmitter.on(‘kongzhi‘, callback);

    // 触发事件 kongzhi 是可以触发的
    eventEmitter.emit(‘kongzhi‘);

    如上基本代码,当我们第一次监听eventEmitter.on(‘kongzhi‘)的时候,existing并没有保存该事件对象函数,因此第一次
    的时候为undefined,所以会做如下判断 如果为undefined的话,就把 callback函数赋值给 events[type]了。
    然后 target._eventsCount 自增1,也就是说 _eventsCount 保存的事件个数加1.
    2. 如果 typeof existing 是个函数的话,说明之前注册过一次 ‘kongzhi‘ 这样的事件,然后继续判断 prepend 是否为
    true还是false,为true的话,说明把该函数插入到数组的最前面,否则的话,插入该数组的后面。如果为true,如下代码:
    events[type] = [listener, existing],否则为false的话,events[type] = [existing, listener], 其中existing
    是保存上一次触发的事件对象。然后所有的判断完成后,把最新值重新赋值给 existing 变量。
    3. 如果 existing 已经是个数组的话,并且prepend为true的话,直接插入到数组的最前面, 因此执行代码:
       existing.unshift(listener);
    4. 其他的情况就是 prepend 默认为false的情况下,且 existing 为数组的情况下,直接把 监听函数 listener 插入到existing该数组的后面去,如下代码: existing.push(listener);
  */
  if (existing === undefined) {
    // Optimize the case of one listener. Don‘t need the extra array object.
    existing = events[type] = listener;
    ++target._eventsCount;
  } else {
    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);
    }
    /*
     下面是监听函数最大的数量。如果监听的数量 m > 0 && existing.length(监听数量的长度大于监听的数量的话) > m
      && !existing.warned 的话。existing.warned 设置为false,目的是打印一次错误即可,因此函数内部代码直接设置
      为 existing.warned = true;比如如下代码:
      // 引入 events 模块
      const events = require(‘events‘);

      console.log(events);

      // 创建 eventEmitter 对象
      const eventEmitter = new events.EventEmitter();

      console.log(eventEmitter);

      const callback = function() {
        console.log(11111);
      };

      // 设置默认监听数量最多为1个,如果超过该数量,控制台会发出警告
      eventEmitter.setMaxListeners(1);

      // 使用on注册监听器
      eventEmitter.on(‘kongzhi‘, callback);

      // 注册kongzhi事件
      eventEmitter.on(‘kongzhi‘, callback);

      // 触发事件 kongzhi
      eventEmitter.emit(‘kongzhi‘);

      设置监听最大个数为1个,但是实际监听的个数为2,因此在控制台中会输出错误,如下报错信息:
      (node) warning: possible EventEmitter memory leak detected. 2 listeners added. Use emitter.setMaxListeners() to increase limit.

      如上错误就是下面的代码的new Error抛出的。最后通过 执行如下函数抛出,如下代码:
      function ProcessEmitWarning(warning) {
        if (console && console.warn) console.warn(warning);
      }
    */
    // 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;
}

3. emit 触发事件的源码如下:

/*
 1. 定义一个args数组,来保存所有的emit后面的参数。比如如下代码:
  // 引入 events 模块
  const events = require(‘events‘);

  console.log(events);

  // 创建 eventEmitter 对象
  const eventEmitter = new events.EventEmitter();

  console.log(eventEmitter);

  // 注册 kongzhi 事件
  eventEmitter.on(‘kongzhi‘, function(age) {
    console.log(‘dddd‘ + age); // 打印 dddd30
  });

  // 触发kongzhi事件
  eventEmitter.emit(‘kongzhi‘, 30);

  如上代码,emit上的第二个参数传递了30,因此在on监听该对象的时候,该函数会接收age这个参数值为30,因此会打印
  dddd30.
  如下代码:for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); 就是从第一个参数开始
  ,使用 arguments.length 获取函数中所有的参数,依次循环保存到 args数组里面去。注意这边 i 是从1开始的,那么
  位置0就是type(类型)。除了触发类型之后所有的变量,也就是所有的参数保存到 args数组内部。
  2. 然后判断 type 是否等于 error, 如 var doError = (type === ‘error‘); 这句代码会返回一个布尔值。
  var events = this._events;保存所有的事件对象,如果 type === ‘error’的话,比如 doError 为true的话,
  如果args数组保存的第一个值是Error的实列的话,就抛出该error。否则的话,就抛出如下错误信息:
  events.js:62 Uncaught Error: Uncaught, unspecified "error" event. (undefined)
  如下代码使用 emit来监听 error事件会打印如上的error代码的。如下:
  // 引入 events 模块
  const events = require(‘events‘);

  console.log(events);

  // 创建 eventEmitter 对象
  const eventEmitter = new events.EventEmitter();

  console.log(eventEmitter);

  eventEmitter.emit(‘error‘);

  如果触发emit的事件不是error的话,那么获取该事件的对象,如下代码:var handler = events[type];
  如果该事件对象没有函数的话,直接返回,监听对象 on中会报错 listener 必须为一个函数的错误。
  如果该handler是一个函数的话,就会执行这个函数。如:ReflectApply(handler, this, args);代码;
  ReflectApply 封装的源码在events.js中的最上面代码:
  var R = typeof Reflect === ‘object‘ ? Reflect : null
  var ReflectApply = R && typeof R.apply === ‘function‘
    ? R.apply
    : function ReflectApply(target, receiver, args) {
      return Function.prototype.apply.call(target, receiver, args);
    }
  想要了解 Reflect.apply的方法的话,可以看我这篇文章 (https://www.cnblogs.com/tugenhua0707/p/10291909.html#_labe2). 因此就能直接执行该回调函数。
  2. 如果handler不是一个函数的话,而是一个数组的话,那么就循环该数组,然后依次执行对应的函数。
*/
EventEmitter.prototype.emit = function emit(type) {
  var args = [];
  for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
  var doError = (type === ‘error‘);

  var events = this._events;
  if (events !== undefined)
    doError = (doError && events.error === undefined);
  else if (!doError)
    return false;

  // If there is no ‘error‘ event listener then throw.
  if (doError) {
    var er;
    if (args.length > 0)
      er = args[0];
    if (er instanceof Error) {
      // Note: The comments on the `throw` lines are intentional, they show
      // up in Node‘s output if this results in an unhandled exception.
      throw er; // Unhandled ‘error‘ event
    }
    // At least give some kind of context to the user
    var err = new Error(‘Unhandled error.‘ + (er ? ‘ (‘ + er.message + ‘)‘ : ‘‘));
    err.context = er;
    throw err; // Unhandled ‘error‘ event
  }

  var handler = events[type];

  if (handler === undefined)
    return false;

  if (typeof handler === ‘function‘) {
    ReflectApply(handler, this, args);
  } else {
    var len = handler.length;
    var listeners = arrayClone(handler, len);
    for (var i = 0; i < len; ++i)
      ReflectApply(listeners[i], this, args);
  }
  return true;
};

4. 删除指定的事件监听器removeListener源码如下:

// 如下基本代码:removeListener的别名是off。

EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
  var list, events, position, i, originalListener;

  /* 如果removeListener方法中第二个参数不是一个函数的话,抛出错误。必须为一个函数。*/
  if (typeof listener !== ‘function‘) {
    throw new TypeError(‘The "listener" argument must be of type Function. Received type ‘ + typeof listener);
  }
  /*
   获取所有的事件对象,保存到 events变量内。如果事件对象是undefined的话,直接返回。
   然后该事件对象 list = events[type]; type 是要被删除的事件名,因此 list 就是保存对应的函数了。
   1. 如果list对象函数等于 listener 要删除的对象函数的话,或者 list.listener === listener 的话;
      或者匹配的是监听事件key: Function
   如下代码:
   // 引入 events 模块
    const events = require(‘events‘);
    console.log(events);
    // 创建 eventEmitter 对象
    const eventEmitter = new events.EventEmitter();
    console.log(eventEmitter);
    const callback = function() {
      console.log(11111);
    };
    // 使用on注册监听器
    eventEmitter.on(‘kongzhi‘, callback);
    // 触发事件 kongzhi
    eventEmitter.emit(‘kongzhi‘);
    eventEmitter.removeListener(‘kongzhi‘, callback);
    /*
     我们继续触发事件 kongzhi 是不会触发的,因为上面已经使用
     removeListener 已经删除了 kongzhi 事件了
     */
    eventEmitter.emit(‘kongzhi‘);
  */
  events = this._events;
  if (events === undefined)
    return this;

  list = events[type];
  if (list === undefined)
    return this;

  if (list === listener || list.listener === listener) {
    /*
     如果删除的事件相等的话,--this._eventsCount 自减1. 同时判断 this._eventsCount 如果等于0的话,
     则移除所有监听,这里重置监听对象数组。即 this._events = Object.create(null);
    */
    if (--this._eventsCount === 0)
      this._events = Object.create(null);
    else {
      /*
       正常执行删除:delete events[type].
       在页面上我们可以来监听 removeListener 事件,比如使用 removeListener 删除事件,我们可以使用on
       来监听 removeListener 事件,比如如下代码:
       // 引入 events 模块
        const events = require(‘events‘);
        console.log(events);
        // 创建 eventEmitter 对象
        const eventEmitter = new events.EventEmitter();
        console.log(eventEmitter);
        const callback = function() {
          console.log(11111);
        };
        // 使用on注册监听器
        eventEmitter.on(‘kongzhi‘, callback);
        // 触发事件 kongzhi
        eventEmitter.emit(‘kongzhi‘);
        eventEmitter.on(‘removeListener‘, function(type, listener) {
          console.log(‘这里是来监听删除事件的‘); // 会打印出来
          console.log(type);  // kongzhi
          console.log(listener); // callback对应的函数
        });
        eventEmitter.removeListener(‘kongzhi‘, callback);
        /*
         我们继续触发事件 kongzhi 是不会触发的,因为上面已经使用
         removeListener 已经删除了 kongzhi 事件了
         */
         eventEmitter.emit(‘kongzhi‘);
      */
      delete events[type];
      if (events.removeListener)
        this.emit(‘removeListener‘, type, list.listener || listener);
    }
  } else if (typeof list !== ‘function‘) {
    /*
     这里是其他情况,如果list监听的不是一个函数的话,而是一个数组的话,比如on监听的多个相同的事件名的时候,则遍历该数组,同样判断,如果该数组的任何一项等于被删除掉的 listener函数的话,
     或者数组中的任何一项的key===listener 等于被删除掉的listener函数的话,使用 originalListener = list[i].listener; 保存起来,同样使用 position = i; 保存该对应的位置。跳出for循环。
     2. if (position < 0) 如果该 position 小于0的话,直接返回。说明没有要删除的事件。
     3. 如果position === 0的话,则删除数组中的第一个元素,使用 list.shift(),移除数组中的第一个元素。
     4. 否则的话调用 spliceOne 方法,删除数组list中的对应位置的元素,该spliceOne方法代码如下:
       function spliceOne(list, index) {
         for (; index + 1 < list.length; index++)
           list[index] = list[index + 1];
         list.pop();
       }
    */
    position = -1;

    for (i = list.length - 1; i >= 0; i--) {
      if (list[i] === listener || list[i].listener === listener) {
        originalListener = list[i].listener;
        position = i;
        break;
      }
    }

    if (position < 0)
      return this;

    if (position === 0)
      list.shift();
    else {
      spliceOne(list, position);
    }
    // 如果list.length === 1 的话,events[type] = list[0].
    if (list.length === 1)
      events[type] = list[0];

    if (events.removeListener !== undefined)
      this.emit(‘removeListener‘, type, originalListener || listener);
  }

  return this;
};

EventEmitter.prototype.off = EventEmitter.prototype.removeListener;

5. 删除所有的监听器 removeAllListeners 源码如下:

/*
 removeAllListeners该函数接收一个参数type,事件名称。
*/
EventEmitter.prototype.removeAllListeners =
function removeAllListeners(type) {
  var listeners, events, i;

  events = this._events;
  // 如果 保存的事件对象 events 为undefined的话,直接返回
  if (events === undefined)
    return this;

  // not listening for removeListener, no need to emit
  /*
   1. 如果 this._events保存的事件对象的removeListener为undefined的话。说明没有使用 removeListener来进行
   删除事件。
   2. 如果  arguments.length === 0 则是删除所有的事件。因为如果removeAllListeners没有指定事件名称的话,
   则是删除所有的事件的。因此直接设置 this._events = Object.create(null); 为空对象,并且 this._eventsCount = 0; 监听的事件数量为0.
   3. 如果 events[type] !== undefined 不等于undefined,说明type有的话,则是删除该事件名的所有事件。
   --this._eventsCount 自减1. 并且如果 this._eventsCount === 0 等于0 的话,则:
   this._events = Object.create(null); 设置空对象。
   4. 如果 this._eventsCount 不等于0 的话,则删除该指定的事件的所有事件名称。最后返回该this对象。
  */
  if (events.removeListener === undefined) {
    if (arguments.length === 0) {
      this._events = Object.create(null);
      this._eventsCount = 0;
    } else if (events[type] !== undefined) {
      if (--this._eventsCount === 0)
        this._events = Object.create(null);
      else
        delete events[type];
    }
    return this;
  }

  // emit removeListener for all listeners on all events
  /*
   1. 如果 arguments.length === 0 等于0的话,则是删除所有的事件。先使用 var keys = Object.keys(events);
   获取所有的keys。然后使用for循环依次遍历,得到某一个事件key。然后依次使用 this.removeAllListeners(key);
   方法递归调用删除对应的key。
   2. 最后执行 this._events = Object.create(null); 设置为空对象。置空。
   3. this._eventsCount = 0; 事件的个数设置为0. 最后返回该this对象。
  */
  if (arguments.length === 0) {
    var keys = Object.keys(events);
    var key;
    for (i = 0; i < keys.length; ++i) {
      key = keys[i];
      if (key === ‘removeListener‘) continue;
      this.removeAllListeners(key);
    }
    this.removeAllListeners(‘removeListener‘);
    this._events = Object.create(null);
    this._eventsCount = 0;
    return this;
  }
  /*
   1. 如果该 events[type] 是一个函数的话,则调用 this.removeListener(type, listeners); 删除该事件对应
   的函数。
   2. 其他的情况就是 listeners 不等于 undefined的话。说明是一个数组的话,那么依次循环该数组,然后依次使用
   removeListener方法删除该事件对应的函数。最后返回this对象。使可以链式调用。
  */
  listeners = events[type];

  if (typeof listeners === ‘function‘) {
    this.removeListener(type, listeners);
  } else if (listeners !== undefined) {
    // LIFO order
    for (i = listeners.length - 1; i >= 0; i--) {
      this.removeListener(type, listeners[i]);
    }
  }
  return this;
};

6. 返回指定事件的监听器数组--listeners源码如下:

function _listeners(target, type, unwrap) {
  var events = target._events;

  if (events === undefined)
    return [];

  var evlistener = events[type];
  if (evlistener === undefined)
    return [];
  /*
   1. 如果events[type]是一个函数的话,就返回 [evlistener.listener || evlistener], 则返回该数组函数。
   2. 否则的话,则返回 该数组函数。
  */
  if (typeof evlistener === ‘function‘)
    return unwrap ? [evlistener.listener || evlistener] : [evlistener];

  return unwrap ?
    unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
}
function unwrapListeners(arr) {
  var ret = new Array(arr.length);
  for (var i = 0; i < ret.length; ++i) {
    ret[i] = arr[i].listener || arr[i];
  }
  return ret;
}
function arrayClone(arr, n) {
  var copy = new Array(n);
  for (var i = 0; i < n; ++i)
    copy[i] = arr[i];
  return copy;
}
EventEmitter.prototype.listeners = function listeners(type) {
  return _listeners(this, type, true);
};

如下代码演示:

// 引入 events 模块
const events = require(‘events‘);

console.log(events);

// 创建 eventEmitter 对象
const eventEmitter = new events.EventEmitter();

console.log(eventEmitter);

// 注册 kongzhi 事件
eventEmitter.on(‘kongzhi‘, function() {
  console.log(‘dddd1111‘);
});

// 注册 kongzhi 事件
eventEmitter.on(‘kongzhi‘, function() {
  console.log(‘dddd22222‘);
});

// 触发kongzhi事件
eventEmitter.emit(‘kongzhi‘);

const num = eventEmitter.listeners(‘kongzhi‘);
console.log(num); // 返回监听事件的数组

num.forEach((func) => {
  func(); // 我们可以这样指定任何一个事件调用,然后会分别打印 dddd1111, dddd2222
});

7. listenerCount返回注册了指定事件的监听数量,源码如下:

EventEmitter.listenerCount = function(emitter, type) {
  if (typeof emitter.listenerCount === ‘function‘) {
    return emitter.listenerCount(type);
  } else {
    return listenerCount.call(emitter, type);
  }
};

EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
  var events = this._events;

  if (events !== undefined) {
    var evlistener = events[type];

    if (typeof evlistener === ‘function‘) {
      return 1;
    } else if (evlistener !== undefined) {
      return evlistener.length;
    }
  }

  return 0;
}

还有其他几个简单的方法 once, eventNames 可以自己看下源码了。

原文地址:https://www.cnblogs.com/tugenhua0707/p/10428807.html

时间: 2024-10-07 09:20:56

深入理解 Node.js 中 EventEmitter源码分析(3.0.0版本)的相关文章

Vue系列---理解Vue.nextTick使用及源码分析(五)

_ 阅读目录 一. 什么是Vue.nextTick()? 二. Vue.nextTick()方法的应用场景有哪些? 2.1 更改数据后,进行节点DOM操作. 2.2 在created生命周期中进行DOM操作. 三. Vue.nextTick的调用方式如下: 四:vm.$nextTick 与 setTimeout 的区别是什么? 五:理解 MutationObserver 六:nextTick源码分析 回到顶部 一. 什么是Vue.nextTick()? 官方文档解释为:在下次DOM更新循环结束之

深度理解Android InstantRun原理以及源码分析

深度理解Android InstantRun原理以及源码分析 @Author 莫川 Instant Run官方介绍 简单介绍一下Instant Run,它是Android Studio2.0以后新增的一个运行机制,能够显著减少你第二次及以后的构建和部署时间.简单通俗的解释就是,当你在Android Studio中改了你的代码,Instant Run可以很快的让你看到你修改的效果.而在没有Instant Run之前,你的一个小小的修改,都肯能需要几十秒甚至更长的等待才能看到修改后的效果. 传统的代

理解 Node.js 中 Stream(流)

Stream(流) 是 Node.js 中处理流式数据的抽象接口. stream 模块用于构建实现了流接口的对象. Node.js 提供了多种流对象. 例如,对 HTTP 服务器的request请求和 process.stdout(标准输出), 都是流的实例. 流可以是可读的.可写的.或者可读可写的. 所有的流都是 EventEmitter 的实例. Stream 的4种类型 1. Readable - 可读的流(fs.createReadStream()) 2. Writable - 可写的流

Java中HashMap源码分析

一.HashMap概述 HashMap基于哈希表的Map接口的实现.此实现提供所有可选的映射操作,并允许使用null值和null键.(除了不同步和允许使用null之外,HashMap类与Hashtable大致相同)此类不保证映射的顺序,特别是它不保证该顺序恒久不变. 值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap. Map map = Collections.sync

Java中ArrayList源码分析

一.简介 ArrayList是一个数组队列,相当于动态数组.每个ArrayList实例都有自己的容量,该容量至少和所存储数据的个数一样大小,在每次添加数据时,它会使用ensureCapacity()保证容量能容纳所有数据. 1.1.ArrayList 的继承与实现接口 ArrayList继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口. public class  ArrayList<E> ex

Spark中决策树源码分析

1.Example 使用Spark MLlib中决策树分类器API,训练出一个决策树模型,使用Python开发. """ Decision Tree Classification Example. """from __future__ import print_functionfrom pyspark import SparkContextfrom pyspark.mllib.tree import DecisionTree, DecisionT

Mybatis中selectKey源码分析

刚回答了一个问题这样一个问题,mybatis不能正常返回主键增加值  下面通过源码分析一下selectKey都具体实现:关于Mybatis 基于注解Mapper源码分析 可以看一下具体解析过程. @Insert("insert into table2 (name) values(#{name})") @SelectKey(statement="call identity()", keyProperty="nameId", before=false

Android 资源加载Resources源码分析(8.0)

我们熟悉的资源加载代码: 1.Activity.getResources(); 2.Context.getResources(); 这2种方式获取的都是Resources对象 先看第一种获取Resources对象源码分析: 说明:(AppcompatActivity中getResource()方法与Activity.getResources()是有区别的.AppcompatActivity是new Resources(...)对象) 一:Activity.getResources()源码分析:

JDK1.8 HashMap中put源码分析

一.存储结构      在JDK1.8之前,HashMap采用桶+链表实现,本质就是采用数组+单向链表组合型的数据结构.它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置.HashMap通过key的hashCode来计算hash值,不同的hash值就存在数组中不同的位置,当多个元素的hash值相同时(所谓hash冲突),就采用链表将它们串联起来(链表解决冲突),放置在该hash值所对应的数组位置上.结构图如下:     图中,紫色部分代表哈希表,也称为哈希数组,数组中每个元素