nodejs核心模块–events

eventsnodejs中很重要的模块,也是nodejs其他很多重要模块的基础,后面当我们看到http模块,socket模块时,我们就会经常碰到一些事件的发出以及监听,它们都是基于events进行的扩展。所以说,events模块,是nodejs事件的基石。

方法汇总

与之前的方式相同,我们先看一下,events模块,对外公开的接口有哪些。依然是最简单的方法去验证:


var events = require("events");
console.log(events);
//usingDomains
//EventEmitter


相较于其他核心模块,events模块对外公开的接口就有点少了,仅仅只有两个。

而其中的usingDomains只是一个属性,取值为true/false。因为我本地的nodejsv0.10.28版本的,而根据v0.10.29版本的源码看来,usingDomains已经不在是对外公开的接口了。v0.10.29版本中,events模块只对外公开了EventEmitter接口,usingDomains属性称为了EventEmitter构造函数的一个静态属性。

所以,在v0.10.28v0.10.29版本中,下面的代码理论上,就会有不同的输出:


var events = require("events");

console.log(events.EventEmitter.usingDomains);  //undefined
console.log(events.usingDomains);   //false


上述的输出是在v0.10.28,而v0.10.29之后,输出的值,就正好是想反的。

既然在v0.10.29版本之后,events模块,只剩下了EventEmitter模块,那么这里,我们也只对该方法进行简单的介绍。

EventEmitter

EventEmitter是一个构造函数,如果我们想要使用它的各项功能,那么就需要把这个构造函数进行实例化,然后使用实例化之后的对象进行处理。

下面就先看看EventEmitter被实例化之后的对象,都有哪些个功能吧。依然使用上面说到的方法:


var events = require("events"),
    emitter = new events.EventEmitter();

console.log(emitter)
//{domain:null,_events:{},_maxListeners:10}


只是很遗憾的是,使用console.log直接查看只能看到三个属性,其中domain的值,跟usingDomains有关,当usingDomains的值为true时,domain就会有新的取值,_events则表示,该实例中,绑定的事件集合。_maxListeners属性则表示,一个实例中,最多可以绑定多少个事件。

接下来,我们继续看看EventEmitter实例上的其他的方法。其实也就是在prototype中存在的属性了,可以直接console.log(events.EventEmitter.prototype)查看到,所有支持的方法,包括:


addListener
on
once
removeListener
removeAllListeners
setMaxListeners
listeners
emit


可是,这里为什么只能以构造函数的原型链查看属性呢。这个说起来跟我们JS中的构造函数继承是相同的原理了。实例化之后,其实质就变成了一个单纯的对象。而不是一个function对象,所以,实例化之后的对象,是没有prototype属性的,而我们只能通过”__proto__“这个属性,去访问继承得到的原型属性,所以,如果我们想要使用实例化看到继承来的属性的话,可以直接console.log(emitter.__proto__)查看即可,获取的结果,和上述结果是完全相同的,甚至,我们可以直接进行一个这样的验证:


console.log(emitter.__proto__  === events.EventEmitter.prototype);//true


这也表明了,实例化的对象中,__proto__指向的对象,就是构造函数的prototype对象。

下面就对上述的方法,进行分别的说明

on,addListener

之所以把这两个放在一起进行说明,是因为,它们实质上完全相同的,但是这里为什么使用两个名称我也想不到具体的原因,估计addListener是为了能和浏览器中的DOM2级事件绑定的方法有个连贯吧,这样更容易理解这个方法的功能了,而on方法的话,还有之后的once方法,我觉得可能是因为在一些JS框架中,都把绑定事件,进行简化使用on,once,所以才这么写的吧。

为什么这么确定他们是完全相同的,这就得看下events模块的源码中,关于他们的实现了,查看源码(events源码

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

看到上面的这一行代码,还有什么可想的么,完全相同的功能,只是却叫了两个名字。下面的代码中,为了方便起见,就只是要on来进行事件的绑定。

想必大家对于JS框架中的jQuery都是有所了解的,那么这里关于on方法的使用,说起来也就是手到擒来吧,例如就像下面这样,给emitter实例中绑定一个privateEvent,可以这么写:


var events = require("events");
var emitter = new events.EventEmitter();

emitter.on("privateEvent",function(){
    console.log("this is a private event!");
});

console.log(emitter);//可以在emitter的_events属性中,就能找到privateEvent事件了。


Ok,就是这么简单,绑定一个事件,当然,如果我们只是绑定一个事件,而不进行触发的,那么就是在白忙活吗,所以为了触发这个绑定的私人事件,就需要我们执行对应的代码,去触发这个事件,所以也就有了后面将要继续说的emit方法,这个后面再进行说明,这里我们先看看在nodejs中,对应事件绑定,是如何进行绑定的,下面就看下events模块的源码中,是如何定义on方法的。


EventEmitter.prototype.addListener = function addListener(type, listener) {
  var m;

  //判断回调函数,是否为一个函数,如果不是,则抛出一个类型错误
  if (!util.isFunction(listener))
    throw TypeError('listener must be a function');
	
  //初始化事件对象列表,如果不存在,则创建该列表
  if (!this._events)
    this._events = {};

  // To avoid recursion in the case that type === "newListener"! Before
  // adding it to the listeners, first emit "newListener".
  //该处是判断,是否有新的事件加入的一个条件判断。
  //如果在_events中,有newListener事件类型的话,当再有事件添加发生时
  //就会通知newListener类型的监听事件。
  if (this._events.newListener)
    this.emit('newListener', type,
              util.isFunction(listener.listener) ?
              listener.listener : listener);

  //判断,是否已经有了该类型的事件被绑定了。如果没有,则直接添加
  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.
	//如果有了该类型的事件,并且事件的回调还是一个数组
	//表示,这个事件已经至少有两个监听事件了,那么就可以把新的回调推入这个数组
    this._events[type].push(listener);
  else
    // Adding the second element, need to change to array.
	//除了以上的两种,那么就剩下一种可能了,就是只有一个回调
	//当有新的回调添加时,就得把回调列表变成一个数组了。
    this._events[type] = [this._events[type], listener];

  // Check for listener leak
  //判断回调是否溢出,当监听该中类型事件的个数(回调的个数)多于设定的值时,就会导致监听溢出,
  //这个监听的事件的个数,可以由之前讲个的setMaxListeners(n)方法设置
  if (util.isObject(this._events[type]) && !this._events[type].warned) {
    var m;
    if (!util.isUndefined(this._maxListeners)) {
      m = this._maxListeners;
    } else {
      m = EventEmitter.defaultMaxListeners;
    }

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


那就看下面的例子了。最简单的实现,以及代码中,我们看到的newListener事件监听。


var events = require("events");
var emitter = new events.EventEmitter();
emitter.on("newListener",function(data,fn){
    fn(data);
});
emitter.on("privateEvent1",function(data){
    console.log(data);
});
emitter.emit("privateEvent1",{name:"zhang"});
emitter.on("privateEvent2",function(data){
    console.log(data);
});

//privateEvent1
//{name:"zhang"}
//privateEvent2


从代码中,我们可以看到,newListener的事件监听,是接收两个参数,第一个参数,也就是其中的data值,是新添加事件监听的类型,第二个参数是新添加监听的回调方法。

并且,只要有事件绑定,就会触发newListener的事件监听,这个可以用来统计添加的监听事件。

removeListener,removeAllListeners

前面看到了nodejs中的事件绑定的方法,现在再看看移除事件的方法。直接看表面意思,也能很明显的了解到,如果有使用过jQuery等框架,就更应该对这个有个了解了,绑定事件,但是该事件监听只会执行一次。

看到这里,对于前端的人来说,很明显的就想到了一些关于removeListener的用法了吧,因为在JS中的DOM2级事件移除中,W3C标准中,就是使用的removeListener的方法进行事件移除的,并且,事件移除不能移除匿名函数。

这里,removeListenerJS中的用法差不多是完全相同的,唯一的一点区别在于,当nodejs中,触发removeListener时,会同时emit一个removeListener的事件,如果有监听该事件的事件监听方法存在,就会捕获到该移除事件的信息,用法和绑定事件中的newListener相同。

先看下nodejs中,源码中,怎么实现的removeListener


// emits a 'removeListener' event if the listener was removed
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;

  list = this._events[type];
  length = list.length;
  position = -1;

  //对该类型的监听回调进行判断处理。并使用position记录
  //需要删除的回调函数,是排在哪个位置的。
  if (list === listener ||
      (util.isFunction(list.listener) && list.listener === listener)) {
    delete this._events[type];
    if (this._events.removeListener)
      this.emit('removeListener', type, listener);
	  //当删除了一个事件之后,就会发送一个removeListener类型的事件

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

    if (list.length === 1) {
      list.length = 0;
      delete this._events[type];
    } else {
      list.splice(position, 1);
    }

    if (this._events.removeListener)
      this.emit('removeListener', type, listener);
  }

  return this;
};


浏览一上面的源代码,也大概能了解到了removeListener的运行步骤了吧,同时,也看到了removeListenerJSremoveListener的区别了。

下面看个小的例子吧:


var events = require("events");
var emitter = new events.EventEmitter();
emitter.on("removeListener",function(data,fn){
    console.log("removeListener type :"+data);
    //添加监听解除事件的处理。
});

function callback(data){
    //privateEvent事件的回调函数,并且在回调函数内
    //解除privateEvent事件的绑定,以此触发removeListener事件
    console.log(data);
    emitter.removeListener("privateEvent",callback);
}
emitter.on("privateEvent",callback);
emitter.emit("privateEvent",{name:"zhang"});
//所以,执行上面的代码,就会出现下面的信息提示:
//{name:"zhang"}
//removeListener type :privateEvent


OK,到这里,removeListener也没有更多想要说明的了。再看下removeAllListeners的情况,我觉得依然可以从源码中,来看这个功能是怎么实现,以及是怎么工作的:


EventEmitter.prototype.removeAllListeners =
    function removeAllListeners(type) {
  var key, listeners;

  //如果没有事件队列,就退出
  if (!this._events)
    return this;

  // not listening for removeListener, no need to emit
  if (!this._events.removeListener) {
  //在移除事件时,是否需要取记录这个removeListener事件,
  //如果没有removeListener类型的监听事件,
  //则不需要使用emit发出这个事件。
    if (arguments.length === 0)
      this._events = {};
	  //如果type没有值,则把所有的事件清空
    else if (this._events[type])
      delete this._events[type];
	  //如果type有值,并且events中有该类型的事件,则只清空该类型的事件
    return this;
	//结束执行,退出该方法
  }

  // emit removeListener for all listeners on all events
  //如果有removeListener的事件,则需要给所有需要删除的事件
  //发出一个removeListener的事件。
  if (arguments.length === 0) {
    for (key in this._events) {
      if (key === 'removeListener') continue;
      this.removeAllListeners(key);
	  //循环调用removeAllListeners方法,把所有的type类型,都删除掉
    }
    this.removeAllListeners('removeListener');
    this._events = {};
    return this;
  }

  listeners = this._events[type];
  //如果指定了要删除的类型,则把该类型取出
  //使用removeListener的方法,逐个进行删除。

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


once

once是事件绑定中的一个特殊形式。有的时候,一些事件我们只希望它会执行一次,当执行完这一次之后,就不需要再执行这个事件了,为了能完成这个简单的工作,就有了once

看下once中的源代码是怎么实现呢:


EventEmitter.prototype.once = function once(type, listener) {
  //如果监听回调不是一个函数,则抛出一个错误
  if (!util.isFunction(listener))
    throw TypeError('listener must be a function');

  var fired = false;
  //fired用作一个标示符,用来表示是否已经执行过了。

  function g() {
    this.removeListener(type, g);
	//当该事件被触发时,就会执行这个移除绑定事件的方法。

    if (!fired) {
      fired = true;
	  //并且把fired置true。保证就算这个事件被移除失败,也不会被再次触发。
	  //改变listener的内部this指向,执行回调函数。。
      listener.apply(this, arguments);
    }
  }

  g.listener = listener;
  this.on(type, g);
  //使用on方法,绑定事件。

  return this;
};


OK,说起来也挺简单的,就是内部重构一个回调函数,在这个回调函数内部,添加一个解除事件绑定的逻辑,然后把事件绑定的回调函数定义为这个新定义的毁掉函数。

并且,从上述的g函数的代码中,也可以看出,


function g() {
    this.removeListener(type, g);
	
    if (!fired) {
      fired = true;
      listener.apply(this, arguments);
    }
}


这里是首先移除了事件,才执行的once的回调函数的。

实例的话,有而是可以很简单的如下表示:


var events = require("events");
var emitter = new events.EventEmitter();
emitter.on("removeListener",function(data,fn){
    //监听事件移除动作
    console.log("removeListener type :"+data);
});
emitter.on("newListener",function(data,fn){
    //监听事件绑定动作
    console.log("newListener type :"+data);
});
function callback(data){
    console.log(data);
}
emitter.once("privateEvent",callback);
emitter.emit("privateEvent",{name:"zhang"});
emitter.emit("privateEvent",{name:"ling"});

//所以,执行上述的代码,就会出现以下的信息提示:
//newListener type :privateEvent
//removeListener type :privateEvent
//{name:"zhang"}


因为使用的是once绑定的事件,虽然emit出现了两次,但是在第二次时,监听函数已经被移除了,所以只有第一个是有效的。

setMaxListeners

前面我们也看到过,绑定事件是有次数限制的,当超过限制之后,就会出现问题,并抛出错误,默认的每种类似的事件,只能绑定10个处理方法。而如果我们需要绑定更多,或者限制只能绑定更少的事件呢,那么就可以使用setMaxListeners方法去修改了。

setMaxListeners是一个方法,而不是一个属性,所以我们可以直接使用emitter.setMaxListeners(n),设置emitter事件,限制绑定事件的个数为n个。

看源代码中,是怎么改变的呢。想想其实也能想得到的,因为这个其实也就等于一个属性,所以看下面吧:


EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
  if (!util.isNumber(n) || n < 0 || isNaN(n))
    throw TypeError('n must be a positive number');
  this._maxListeners = n;
  //如果传入的值,是一个数字,那么就把_maxListeners属性的值,修改为该数字即可。
  return this;
};


Ok,不多说。

listeners

回调函数的集合,需要传入一个参数type,表示哪一种事件的回调函数集合,最后的输出是一个数组。

不做实例,只看源代码实现


EventEmitter.prototype.listeners = function listeners(type) {
  var ret;
  if (!this._events || !this._events[type])
    ret = [];
	//如果不存在events对象组,不存在该type类型的事件,
	//则直接返回一个空数组
  else if (util.isFunction(this._events[type]))
    ret = [this._events[type]];
	//如果找到的type类型事件的回调函数时一个函数,
	//那么表示只被绑定了一次,变成数组返回
  else
    ret = this._events[type].slice();
	//否则,把它们直接变成一个数组返回。
  return ret;
};


emit

事件的触发方法,也是和监听方法同样无比重要的方法了。在之前我们也看到了该方法的使用。

使用方法如下:


emitter.emit(event, [arg1], [arg2], [...])


event是表示事件的类型。后面的全是需要传递的参数。

而传递的参数,是通过事件监听的回调函数获取,比如:


emitter.on(event,function([arg1], [arg2], [...]){});


回调函数的参数,就对应的是emit中相应的参数。

结束

OK,这里关于events模块的内容就这么结束了。只是很基础的用法。如果有问题,请多指教。

本文地址:http://www.zhangyunling.com/?p=195

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>