详解Node.js:events事件模块


Posted in Javascript onNovember 24, 2016

Nodejs的大部分核心API都是基于异步事件驱动设计的,所有可以分发事件的对象都是EventEmitter类的实例。

大家知道,由于nodejs是单线程运行的,所以nodejs需要借助事件轮询,不断去查询事件队列中的事件消息,然后执行该事件对应的回调函数,有点类似windows的消息映射机制。至于更细的实现环节,可以另行查找资料。

下面介绍EventEmitter的使用。

1、监听事件和分发事件

EventEmitter实例可以使用onaddListener监听事件,emit()方法分发事件,如下所示:

const events = require('events'),
   EventEmitter = events.EventEmitter,
   util = require('util');
function myEmiter(){
  EventEmitter.call(this);
};
util.inherits(myEmiter,EventEmitter);//继承EventEmitter类
const myEmitterIns = new myEmiter();
myEmitterIns.on('data',(o)=>{
  console.log('receive the data:'+o.a);
});

或者使用class类

class myEmiter extends EventEmitter{}//继承EventEmitter类
const myEmitterIns = new myEmiter();

myEmitterIns.on('data',(o)=>{
  console.log('receive the data:'+o.a);
});
myEmitterIns.emit('data',{a:1});

执行结果如下:

E:\developmentdocument\nodejsdemo>node event-example.js
receive the data:1

2、向事件监听回调函数传递参数

从上面的例子可以看出,emit()方法可以传递任意的参数集合给回调函数,需要注意的一点是this关键字指向的是调用emit方法的EventEmiter实例,但在箭头函数中例外,this指向的是全局this,因为箭头函数中的this是在定义时绑定。如下所示:

class myEmiter extends EventEmitter{}
const myEmitterIns = new myEmiter();
myEmitterIns.on('data',function(data){
  console.log("普通回调函数中this:");
  console.log(this);
});
myEmitterIns.on('data1',(data1)=>{
  console.log("箭头回调函数中this:");
  console.log(this);
});
myEmitterIns.emit('data',{a:1});
myEmitterIns.emit('data1',{a:1});

执行结果如下:

E:\developmentdocument\nodejsdemo>node event-example.js
普通回调函数中this:
myEmiter {
domain: null,
_events: { data: [Function], data1: [Function] },
_eventsCount: 2,
_maxListeners: undefined }
箭头回调函数中this:
{}

这里讲到箭头函数中的this,就顺便说一下,为什么箭头函数能够实现定义时绑定this,就是因为箭头函数内部根本就没有绑定this的机制,它使用的是外层作用域的this,因此它也不能作为构造函数。

3、事件监听程序的执行顺序

EventEmiter实例可以绑定多个事件,当我们顺序触发这些事件时,EventEmiter会以同步模式执行,既第一个事件处理函数没有完成,便不会触发下一个事件,如下所示:

class myEmiter extends EventEmitter{}
const myEmitterIns = new myEmiter();
myEmitterIns.on('data',function(data){
  console.time('data事件执行了');
  for(var i = 0 ; i< 100000; i++)
    for(var j = 0 ; j< 100000; j++)
      ;
  console.timeEnd('data事件执行了');
});
myEmitterIns.on('data1',(data1)=>{
  console.log("data1事件开始执行...");
});
myEmitterIns.emit('data',{a:1});
myEmitterIns.emit('data1',{a:1});

执行结果如下:

E:\developmentdocument\nodejsdemo>node event-example.js
data事件执行了: 4721.401ms
data1事件开始执行...

当然我们可以在回调函数中使用异步操作,例如setTimeout,setImmediate或者process.nextTick()等,从而实现异步的效果,如下所示:

myEmitterIns.on('data',function(data){
  setImmediate(()=>{
    console.log('data事件执行了...');
  });
});

执行结果如下:

E:\developmentdocument\nodejsdemo>node event-example.js
data1事件执行了...
data事件执行了...

4、一次性事件监听

EventEmiter可以使用once监听某个事件,则该事件处理程序只会触发一次,之后emit该事件都会被忽略,因为监听程序被注销了,如下所示:

myEmitterIns.once('one',(data)=>{
  console.log(data);
});
myEmitterIns.emit('one','this is first call!');
myEmitterIns.emit('one','this is second call!');

执行结果如下:

E:\developmentdocument\nodejsdemo>node event-example.js
this is first call!

从上面的结果看出,'one'事件只执行了一次。

5、移除事件绑定

类似DOM事件监听,EventEmiter也可以移除事件绑定,利用removeListener(eventName,listener)方法解除某个事件的绑定,因此回调函数listener必须是命名函数,要不然找不到该函数,因为函数是引用型类型,就算函数体是一样,也不是同一个函数,如下所示:

myEmitterIns.on('data',function(e){
  console.log(e);
});
myEmitterIns.removeListener('data',function(e){
  console.log(e);
});
myEmitterIns.emit('data','hello data!');
function deal(e){
  console.log(e);
}
myEmitterIns.on('data1',deal);
myEmitterIns.removeListener('data1',deal);
myEmitterIns.emit('data1','hello data1!');

执行结果如下:

E:\developmentdocument\nodejsdemo>node event-example.js
hello data!
E:\developmentdocument\nodejsdemo>

从执行结果可以看出,data事件使用的是匿名函数,因此没有被移除掉,而data1事件则成功解除绑定了。这里需要注意一点的是emit触发某个事件后,所有跟该事件绑定的回调函数都会被调用,即使你在某个回调函数中使用removeListener函数移除掉另一个回调也没有用,但是随后的事件队列是移除了该回调的。如下所示:

function dealData1(e){
  console.log('data事件执行了...A');
}
myEmitterIns.on('data',function(e){
  console.log(e);
  myEmitterIns.removeListener('data',dealData1);//这里解除dealData1的绑定
});
myEmitterIns.on('data',dealData1);
myEmitterIns.emit('data','data事件执行了...B');
/*执行结果为:
 data事件执行了...B
 data事件执行了...A
*/
//再次触发该事件时,dealData1回调已经被解除绑定了
myEmitterIns.emit('data','data事件执行了...');
//data事件执行了...

另外可以使用removeAllListeners()解除所有事件的绑定。

6、获取事件监听数量和监听函数

使用emitter.listenerCount(eventName)函数获取指定事件的监听数量,函数emitter.listeners(eventName)则可以用来获取指定事件的所有监听函数,使用如下所示:

var cbA = ()=>{},
  cbB = ()=>{};
var emitter = new myEmiter();
emitter.on('data',cbA);
emitter.on('data',cbB);
console.log('emitter实例的data事件绑定了%d个回调函数',emitter.listenerCount('data'));
console.log('它们是:',emitter.listeners('data'));

执行结果如下:

E:\developmentdocument\nodejsdemo>node event-example.js
emitter实例的data事件绑定了2个回调函数
它们是: [ [Function: cbA], [Function: cbB] ]

7、获取和设置emitter的最大监听数量
nodejs对同一事件的监听数量建议不宜超过10个,这个可以查看EventEmitter.defaultMaxListeners属性便可得知,如下所示:

console.log(EventEmitter.defaultMaxListeners);//结果为10个

emitter通过getMaxListeners()方法获取最大监听数量以及setMaxListeners(n)方法设置最大监听数量,如下所示:

var cbA = ()=>{},
  cbB = ()=>{};
var emitter = new myEmiter();
emitter.setMaxListeners(1);
emitter.on('data',cbA);
emitter.on('data',cbB);
console.log(emitter.getMaxListeners());

执行结果如下:

E:\developmentdocument\nodejsdemo>node event-example.js
emitter的事件最大监听数是:1
(node:6808) Warning: Possible EventEmitter memory leak detected. 2 data listener
s added. Use emitter.setMaxListeners() to increase limit

如上结果所示,如果设置了最大监听数量,则同一事件的监听最好不要超过该最大值,否则很可能发送内存泄漏。

events模块便介绍到这。希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery powerFloat万能浮动层下拉层插件使用介绍
Dec 27 Javascript
如何使用jQuery Draggable和Droppable实现拖拽功能
Jul 05 Javascript
一个js控制的导航菜单实例代码
Dec 03 Javascript
javascript中验证大写字母、数字和中文
Jan 15 Javascript
javascript实现类似超链接的效果
Dec 26 Javascript
JavaScript实现三阶幻方算法谜题解答
Dec 29 Javascript
js实现具有高亮显示效果的多级菜单代码
Sep 01 Javascript
jQuery easyui刷新当前tabs的方法
Sep 23 Javascript
微信小程序 picker-view 组件详解及简单实例
Jan 10 Javascript
jQuery.Form实现Ajax上传文件同时设置headers的方法
Jun 26 jQuery
通过一个简单的例子学会vuex与模块化
Nov 22 Javascript
完美解决手机网页中输入框被输入法遮挡的问题
Dec 19 Javascript
详解微信小程序开发之下拉刷新 上拉加载
Nov 24 #Javascript
微信小程序-消息提示框实例
Nov 24 #Javascript
jQuery实现的模拟弹出窗口功能示例
Nov 24 #Javascript
基于JavaScript实现Tab选项卡切换效果
Nov 24 #Javascript
javascript实现获取图片大小及图片等比缩放的方法
Nov 24 #Javascript
网站发布后Bootstrap框架引用woff字体无法正常显示的解决方法
Nov 24 #Javascript
Javascript 数组去重的方法(四种)详解及实例代码
Nov 24 #Javascript
You might like
用来解析.htgroup文件的PHP类
2012/09/05 PHP
php生成EAN_13标准条形码实例
2013/11/13 PHP
ThinkPHP3.1.3版本新特性概述
2014/06/19 PHP
PHP通过微信跳转的Code参数获取用户的openid(关键代码)
2016/07/06 PHP
PHP操作MySQL中BLOB字段的方法示例【存储文本与图片】
2017/09/15 PHP
php curl获取到json对象并转成数组array的方法
2018/05/31 PHP
javascript YUI 读码日记之 YAHOO.util.Dom - Part.4
2008/03/22 Javascript
jQuery 学习第六课 实现一个Ajax的TreeView
2010/05/17 Javascript
基于jQuery的弹出警告对话框美化插件(警告,确认和提示)
2010/06/10 Javascript
js识别uc浏览器的代码
2015/11/06 Javascript
Bootstrap教程JS插件弹出框学习笔记分享
2016/05/17 Javascript
JS匹配日期和时间的正则表达式示例
2017/05/12 Javascript
解决vue-cli中stylus无法使用的问题方法
2017/06/19 Javascript
基于Vue的ajax公共方法(详解)
2018/01/20 Javascript
微信小程序如何获取手机验证码
2018/11/04 Javascript
vue-loader中引入模板预处理器的实现
2019/09/04 Javascript
Node.JS获取GET,POST数据之queryString模块使用方法详解
2020/02/06 Javascript
小程序表单认证布局及验证详解
2020/06/19 Javascript
JS轮播图的实现方法
2020/08/24 Javascript
JS+CSS实现炫酷光感效果
2020/09/05 Javascript
python机器学习理论与实战(二)决策树
2018/01/19 Python
python爬取网页转换为PDF文件
2018/06/07 Python
python使用百度文字识别功能方法详解
2019/07/23 Python
python英语单词测试小程序代码实例
2019/09/09 Python
python实现批量命名照片
2020/06/18 Python
python使用隐式循环快速求和的实现示例
2020/09/11 Python
Django中日期时间型字段进行年月日时分秒分组统计
2020/11/27 Python
HTML5实现无刷新修改URL的方法
2019/11/14 HTML / CSS
荷兰浴室和卫浴网上商店:Badkamerxxl.nl
2020/10/06 全球购物
工作态度检讨书
2014/02/11 职场文书
《与朱元思书》的教学反思
2014/04/17 职场文书
大学生党员个人剖析材料
2014/10/08 职场文书
感谢师恩主题班会
2015/08/17 职场文书
北京大学中文系教授推荐的10本小说
2019/08/08 职场文书
浅谈GO中的Channel以及死锁的造成
2022/03/18 Golang
教你在 Java 中实现 Dijkstra 最短路算法的方法
2022/04/08 Java/Android