events
是nodejs
中很重要的模块,也是nodejs
其他很多重要模块的基础,后面当我们看到http
模块,socket
模块时,我们就会经常碰到一些事件的发出以及监听,它们都是基于events
进行的扩展。所以说,events
模块,是nodejs
事件的基石。
方法汇总
与之前的方式相同,我们先看一下,events
模块,对外公开的接口有哪些。依然是最简单的方法去验证:
var events = require("events");
console.log(events);
//usingDomains
//EventEmitter
相较于其他核心模块,events
模块对外公开的接口就有点少了,仅仅只有两个。
而其中的usingDomains
只是一个属性,取值为true/false
。因为我本地的nodejs
是v0.10.28
版本的,而根据v0.10.29
版本的源码看来,usingDomains
已经不在是对外公开的接口了。v0.10.29
版本中,events
模块只对外公开了EventEmitter
接口,usingDomains
属性称为了EventEmitter
构造函数的一个静态属性。
所以,在v0.10.28
和v0.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
的方法进行事件移除的,并且,事件移除不能移除匿名函数。
这里,removeListener
和JS
中的用法差不多是完全相同的,唯一的一点区别在于,当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
的运行步骤了吧,同时,也看到了removeListener
和JS
中removeListener
的区别了。
下面看个小的例子吧:
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
模块的内容就这么结束了。只是很基础的用法。如果有问题,请多指教。