Nodejs源码解析之events

Nodejs中的events模块是很常见的模块,其实现了事件注册,通知等功能,是观察者模式的实现。其使用很简单,实例代码如下:

// 导入events模块
var events = require('events');
// 创建EventEmitter对象
var eventEmitter = new events.EventEmitter();

//回调函数1
var listener1 = function listener1() {
   console.log('listener1 executed.');
}

//回调函数2
var listener2 = function listener2() {
  console.log('listener2 executed.');
}

// 注册事件的回调函数, 相当于增加观察者
eventEmitter.addListener('connection', listener1);

// 再次注册同样事件的回调函数, 相当于增加观察者
eventEmitter.on('connection', listener2);

// 触发事件,这个时候,会有函数 listener1 和listener2同时被调用
eventEmitter.emit('connection');

上述代码使用是相当简单的,这个是如何实现的的呢? 其实这个是一般观察者的设计模式的实现逻辑是类似的,都是有一个类似map的结构,存储监听事件和回调函数的对应关系,当监听事件发生时,调用回调函数,这个也不例外。不多说,来看源代码吧。

初始化

// 对象的构造函数
function EventEmitter() {
  //这个就是用于调用Init函数
  // 也就是说说构造函数,仅仅是调用的下面的Init 
  EventEmitter.init.call(this);
}
// 导出函数,这个就是一个events模块的总体导出函数
// 在上述的用法中,我们都是需要创建一个EventEmitter对象的
module.exports = EventEmitter;

// 用于兼容node 0.10.x
EventEmitter.EventEmitter = EventEmitter;
// 是否使用domain,默认用法是不使用。Domain其实是EventEmitter子类,
// 一个单独的模块,这里不进一步分析,有兴趣可以看domain.js
EventEmitter.usingDomains = false;
// 同上,用于domain模块
EventEmitter.prototype.domain = undefined;
// 这个就是用于存储事件和回调的类map对象
EventEmitter.prototype._events = undefined;
// 
EventEmitter.prototype._maxListeners = undefined;

// By default EventEmitters will print a warning if more than 10 listeners // are added to it. This is a useful default which helps finding memory leaks.
// 默认的最大的观察者的个数,默认为10, 如果超过,会有警告信息,以避免内存泄漏
EventEmitter.defaultMaxListeners = 10;

// 初始化,构造函数必须调用的部分
EventEmitter.init = function() {

  // 下面是对domain的处理,不考虑
  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;
    }
  }
   // 创建了一个_events 的空对象,相当于创建了一个map
   if (!this._events || this._events === Object.getPrototypeOf(this)._events)
    this._events = {};
  // 用于保存当前最大监听数目,后面会用到
  this._maxListeners = this._maxListeners || undefined;
};

通过上述的源码分析,可以得出,当我们创建EventEmitter对象时候,其实就是生成了一个_events的空对象,做一些基本的检查。

// 导入events模块
var events = require('events');
// 创建EventEmitter对象
var eventEmitter = new events.EventEmitter();

增/删 监听

首先来查看一下增加监听相关的源代码,这里有三个函数:

  • addListener: 增加事件监听。
  • on: addListener的别名,实际上是一样的。
  • once: 增加事件监听,不同的是,事件被fire一次后,会掉函数不再被执行。
EventEmitter.prototype.addListener = function addListener(type, listener) {
  var m;
   // 首先查看的是否listener为一个函数,确保是可以被执行的会掉函数
  if (!util.isFunction(listener))
    throw TypeError('listener must be a function');

  //确保_events对象已经被创建
  if (!this._events)
    this._events = {};

  // To avoid recursion in the case that type === "newListener"! Before
  // adding it to the listeners, first emit "newListener".
  // 从注释上,这个是防止递归调用的,这里只有定义了newListener后,才会发送事件
  // newListener,从本函数的代码,可以看出,newListener没有被定义,可以忽视
  if (this._events.newListener)
    this.emit('newListener', type,
              util.isFunction(listener.listener) ?
              listener.listener : listener);
  // 查看该type(事件)是否存在,
  if (!this._events[type])
    // Optimize the case of one listener. Don't need the extra array object.
    // 如果不存在,直接存入就可以
    this._events[type] = listener;
  else if (util.isObject(this._events[type]))
    // If we've already got an array, just append.
     // 如果存在,并且是array,直接push
    this._events[type].push(listener);
  else
    // Adding the second element, need to change to array.
    // 如果不是array,生成一个新的array
    this._events[type] = [this._events[type], listener];

  // Check for listener leak
  // 下面的代码就是查看是否监听者超过了最大的数目,这个是关于默认的数目的
  if (util.isObject(this._events[type]) && !this._events[type].warned) {
    var m;
    if (!util.isUndefined(this._maxListeners)) {
      m = this._maxListeners;
    } else {
      m = EventEmitter.defaultMaxListeners;
    }
     //如果数目过大,直接给出console.error
    if (m && m > 0 && this._events[type].length > m) {
      this._events[type].warned = true;
      console.error('(node) warning: possible EventEmitter memory ' +
                    'leak detected. %d %s listeners added. ' +
                    'Use emitter.setMaxListeners() to increase limit.',
                    this._events[type].length, type);
      console.trace();
    }
  }

  return this;
};

// 这里就是说on 和addListener是相互一样的,别名。
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
// 查看once,事件触发一次回调函数,就删除,相当于调用了removeListener
EventEmitter.prototype.once = function once(type, listener) {
  // 依然一样是确保参数为函数,可以回调
  if (!util.isFunction(listener))
    throw TypeError('listener must be a function');
  // 回调函数是否被fire了 
  var fired = false;
  // 帮助函数
  function g() {
    // 删除回调函数,请注意,一旦被fired掉,就删除
    this.removeListener(type, g);
    // 检查是否被fired过了,我想,这个可能是防止重复增加的case,也就是多次调用了once的  情况
    if (!fired) {
      fired = true;
      // 执行回调函数
      listener.apply(this, arguments);
    }
  }
  // 增加一个listener属性为回调函数
  g.listener = listener;
  // 增加具体的回调函数,该回调函数变成了帮助函数g,而不是listener
  this.on(type, g);
  // 返回整个对象
  return this;
};

下面介绍一下删除回调相关的函数。

  • removeListener - 删除具体的事件回调
  • removeAllListeners - 删除具体时间相关的所有的回调
EventEmitter.prototype.removeListener =
    function removeListener(type, listener) {
  var list, position, length, i;

  //依然是检查listener是否为函数
  if (!util.isFunction(listener))
    throw TypeError('listener must be a function');

  // 确保_events是否为空,以及事件存在在对象中
  if (!this._events || !this._events[type])
    return this;
  // 等到回调函数的value
  list = this._events[type];
  length = list.length;
  position = -1;
  // 如果当前的value和要删除的回调是相等的,包含once的内容
  if (list === listener ||
      (util.isFunction(list.listener) && list.listener === listener)) {
    // 直接删除
    delete this._events[type];
    // 发送事件
    if (this._events.removeListener)
      this.emit('removeListener', type, listener);
  } // 如果是队列,直接出来
  else if (util.isObject(list)) {
    for (i = length; i-- > 0;) {
      if (list[i] === listener ||
          (list[i].listener && list[i].listener === listener)) {
        position = i;
        break;
      }
    }
     // 如果查找不到,直接返回
    if (position < 0)
      return this;
     // 如果长度为1,说明可以删除
    if (list.length === 1) {
      list.length = 0;
      delete this._events[type];
    } else {
       // 直接在数组中删除该回调
      list.splice(position, 1);
    }
     // fire removeListener事件
    if (this._events.removeListener)
      this.emit('removeListener', type, listener);
  }

  return this;
};

// 删除与单一事件相关的所有回调函数
EventEmitter.prototype.removeAllListeners =
    function removeAllListeners(type) {
  var key, listeners;
 // 确保_events不为空
  if (!this._events)
    return this;

  // not listening for removeListener, no need to emit
  // 查看当前是否有removeListener的监听者,从本模块看,没有赋值,所以一般情况下,都是
  // 直接删除回调然后返回
  if (!this._events.removeListener) {
    if (arguments.length === 0)
      this._events = {};
    else if (this._events[type])
      delete this._events[type];
    return this;
  }

  // emit removeListener for all listeners on all events
  // 处理没有参数的情况,在没有参数的情况下,就是删除所有事件的回调
  // 相当于清空。 
  if (arguments.length === 0) {
    for (key in this._events) {
      // 注意有特殊情况是,不删除removeListener的回调
      if (key === 'removeListener') continue;
      this.removeAllListeners(key);
    }
    this.removeAllListeners('removeListener');
    this._events = {};
    return this;
  }
  // 处理有具体参数的情况, 找到具体回调函数
  listeners = this._events[type];

  // 如果回调是单个函数,直接删除就好
  if (util.isFunction(listeners)) {
    this.removeListener(type, listeners);
  } else if (Array.isArray(listeners)) {
    // LIFO order
    // 处理回调函数是一个数组的情况,从后往前一个一个删除。
    while (listeners.length)
      this.removeListener(type, listeners[listeners.length - 1]);
  }
  // 清空事件对于的回调函数对象
  delete this._events[type];

  return this;
};

事件触发

事件触发是由emit函数来实现的,具体的含义就是发送一个事件,这个会同步的调用回调函数。 具体的源代码解析如下:

EventEmitter.prototype.emit = function emit(type) {
  var er, handler, len, args, i, listeners;
  // 还是检查当前存储的events队列是否为空
  if (!this._events)
    this._events = {};

  // If there is no 'error' event listener then throw.
  // 这里需要对error进行特殊处理,如果没有error事件的监听者,直接会抛出error的错误,
  // 所有对error的时间的处理要特别注意。
  // 也就是所emit(‘error’)有抛出异常的功能,这个是文档中没有的
  if (type === 'error' && !this._events.error) {
    er = arguments[1];
    // 这个是具体domain的处理
    if (this.domain) {
      if (!er)
        er = new Error('Uncaught, unspecified "error" event.');
      er.domainEmitter = this;
      er.domain = this.domain;
      er.domainThrown = false;
      this.domain.emit('error', er);
    } else if (er instanceof Error) {
      // 直接抛出异常,如果emit一个参数Error的实例
      throw er; // Unhandled 'error' event
    } else {
       抛出生成的异常
      throw Error('Uncaught, unspecified "error" event.');
    }
    return false;
  }
  // 找到对应事件的回调函数
  handler = this._events[type];
 // 如果回调函数没有定义,说明不存在,直接返回false
  if (util.isUndefined(handler))
    return false;
  // 如果是domain的使用,直接进入domain内处理
  if (this.domain && this !== process)
    this.domain.enter();
  //处理是单个的函数的情况。
  if (util.isFunction(handler)) {
    // 检查参数的情况,注意,至少有事件这一个argument。所以先处理1,2,3
    switch (arguments.length) {
      // fast cases
      case 1:
        // 当回调函数没有参数时候
        handler.call(this);
        break;
      case 2:
        // 当回调函数有一个参数时候
        handler.call(this, arguments[1]);
        break;
      case 3:
        // 当回调函数有二个参数时候
        handler.call(this, arguments[1], arguments[2]);
        break;
      // slower
      default:
        // 当回调函数三个或者以上参数时候,就会做一个copy,然后再调用
        // 所以,这里我们可以特别注意的地方是,为了效率考虑,回调函数最好不要用3个或者3个以上的函数参数
        len = arguments.length;
        args = new Array(len - 1);
        for (i = 1; i < len; i++)
          args[i - 1] = arguments[i];
        handler.apply(this, args);
    }
  } // 这里处理回调函数为一个数组的情况
  else if (util.isObject(handler)) {
    // 这里直接生成一个参数的拷贝
    len = arguments.length;
    args = new Array(len - 1);
    for (i = 1; i < len; i++)
      args[i - 1] = arguments[i];
    // 生成一个回调函数的新队列
    listeners = handler.slice();
    len = listeners.length;
    // 逐个调用回调函数
    for (i = 0; i < len; i++)
      listeners[i].apply(this, args);
  }
  // 处理domain 的情况,直接退出
  if (this.domain && this !== process)
    this.domain.exit();
  //返回true,有回调函数处理的情况 
  return true;
};

监听者状态

源码中还提供了下面两个函数,用于查看当前事件的监听状态:

  • listeners - 事件的回调函数,单个或者队列
  • listenerCount - 事件的回调函数的个数

首先看一下listeners的源码

// 具体的实例的原型函数
// 从代码可以看出,listeners返回都是一个数组,可能为空,也可能含有1个或者多个元素
EventEmitter.prototype.listeners = function listeners(type) {
  var ret;
  // 直接查看是否存在,对应的回调函数存在否
  if (!this._events || !this._events[type])
    ret = []; // 不存在,直接返回空队列
  else if (util.isFunction(this._events[type])) // 如果事件对于的值是一个函数
    ret = [this._events[type]]; // 返回只有一个回调函数的数组
  else/ 如果事件对于的值是一个队列
    ret = this._events[type].slice(); // 直接生成回调函数的数组
  return ret; // 返回数组
};

接着看一个listenerCount源代码,

// 这个是一个构造函数的方法,和其他的函数调用有少许的不同。
// 其返回的是个数
EventEmitter.listenerCount = function(emitter, type) {
  var ret;
  // 和上面的函数一样,也是分三种情况检查

  if (!emitter._events || !emitter._events[type])
    ret = 0;// 不存在,直接返回个数为0
  else if (util.isFunction(emitter._events[type]))
    ret = 1;// 如果事件对于的值是一个函数,直接返回1
  else
    ret = emitter._events[type].length;// 如果事件对于的值是一个数组,直接返回数组长度
  return ret;
};

从源码可以看出,使用时候需要注意以下几点:

1.同一事件的监听函数不要太多,默认为10, 过大会报警或者错误
2.同一事件的监听函数最好是1个,否则效率会慢。
3.回调函数的参数最好在2个以内,否则效率会慢。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在Linux下安装Node.js源码有多种方法。其中一种方法是通过建立软连接来使其可以在全局被访问到。首先,你需要使用以下命令建立软连接: ``` ln -s (自己存放nodejs的路径)nodejs/bin/node /usr/local/bin/ ln -s (自己存放nodejs的路径)nodejs/bin/npm /usr/local/bin/ ``` 这样就可以将Node.js的可执行文件和npm命令链接到/usr/local/bin/目录下,使其可以在全局被访问到。\[1\] 另一种方法是通过编译Node.js源码来安装Node.js。然而,在编译过程中可能会遇到各种编译错误问题,很难解决。因此,这种方法并不推荐。\[2\] 还有一种方式是使用包管理器来安装Node.js,比如使用yum install node或者apt-get install node命令来安装。但是需要注意的是,在Linux下默认源中可能没有最新版的Node.js程序,因此这种方式可能安装的是不是最新版的Node.js。\[3\] #### 引用[.reference_title] - *1* *2* [Linux服务器安装NodeJs简易方法](https://blog.csdn.net/weixin_44248258/article/details/124054432)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [linux下安装nodejs的方式](https://blog.csdn.net/u011296285/article/details/128370859)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值