当前位置: 首页 > 后端技术 > Node.js

Node.jsEventEmitter类源码分析

时间:2023-04-03 15:30:49 Node.js

写在最上面这次尝试分析Node.js中EventEmitter模块的事件机制,分析Node.js中实现发布-订阅模式的一些细节。单击此处获取完整的Node.js源代码。欢迎来到我的博客,该博客不时更新——EventEmitter大多数Node.js核心API使用惯用的异步事件驱动架构,其中某些类型的对象(触发器)定期触发命名事件以调用函数对象(侦听器)。例如,net.Server对象在每次有新连接时触发一个事件;fs.ReadStream在打开文件时触发一个事件;当数据可供读取时,流对象会触发一个事件。所有可以发出事件的对象都是EventEmitter类的实例。Node.js中EventEmitter类实例的使用可以说是贯穿了整个Node.js,相信大家对此已经很熟悉了。其中使用的发布-订阅模型是管理消息分发的经典方式。在这种模式下,消息发布方不需要知道消息将发送给谁,订阅方也不需要知道消息的来源。用法一般如下:constEventEmitter=require('events');classMyEmitterextendsEventEmitter{}constmyEmitter=newMyEmitter();myEmitter.on('event',()=>{console.log('triggered一个事件A!');});myEmitter.emit('event');//事件A被触发!当我们订阅了'event'事件后,我们可以在任何地方通过emit('event')执行事件回调。EventEmitter相当于一个中介,负责记录订阅了哪些事件,触发了什么回调。当接收到事件被触发时,回调将被一个一个地执行。发布订阅模式从源码看EventEmitter类是如何实现发布订阅的。首先我们梳理一下实现这个模式需要的步骤:初始化空对象存放监听事件和对应的回调函数来添加监听事件,注册回调函数触发事件,找出对应的回调函数队列,执行监听事件的删除一一初始化空对象生成空对象的方式,一般容易想到直接给空对象赋值,即vara={};,Node.js中使用的方法是vara=Object。create(null),理论上使用这个方法访问对象的属性应该会更快。出于好奇,作者对这两种方法做了一个粗略的比较:vara={}a.test=1varb=Object.create(null)b.test=1console.time('{}')for(vari=0;i<1000;i++){console.log(a.test)}console.timeEnd('{}')console.time('create')for(vari=0;i<1000;i++){console.log(b.test)}console.timeEnd('create')打印结果,好像直接赋一个空对象和通过Object.create没有什么区别,性能上有很大的区别,没有人占了上风。就目前使用空对象来存放注册的监听事件和回调而言,如果直接使用{}来初始化this._events,对性能的影响可能不会很大。但这只是个人看法,我还无法理解Node.js中这样使用的深层含义。添加监听事件,注册回调函数EventEmitter.prototype.addListener=functionaddListener(type,listener){return_addListener(this,type,listener,false);};EventEmitter.prototype.on=EventEmitter.prototype.addListener;添加监听器方法是addListener,on是它的别名。if(!existing){//优化一个监听器的情况。不需要额外的数组对象。现有=事件[类型]=侦听器;++target._eventsCount;}else{if(typeofexisting==='function'){//添加第二个元素,需要改为数组。现有=事件[类型]=前置?[听众,现有]:[现有,听众];}else{//如果我们已经有了一个数组,只需追加即可。如果(前置){existing.unshift(监听器);}else{existing.push(listener);}}...}如果之前没有监听事件,则进入第一次判断,其中type为事件类型,listener为触发的事件回调。如果事件之前已经注册过,回调函数会被添加到回调队列的头部或尾部。看到下面的打印结果:myEmitter.on('event',()=>{console.log('一个事件A被触发了!');});myEmitter.on('event',()=>{console.log('事件B被触发!');});myEmitter.on('talk',()=>{console.log('事件CS被触发!');//myEmitter.emit('talk');});console.log(myEmitter._events)//{event:[[Function],[Function]],talk:[Function]}myEmitter实例的_events方法是我们存放事件的对象,回调。可以看出,当我们依次注册事件时,回调会被推送到_events对应的key的值。触发一个事件,找出对应的回调函数队列,在被触发的emit函数中一个一个执行,触发时根据传入的参数个数执行不同的函数:(直接执行不同参数的不同函数,这个操作应该会让性能更好Ok,但是作者没有测试这个)switch(len){//fastcasescase1:emitNone(handler,isFn,this);休息;案例2:emitOne(handler,isFn,this,arguments[1]);休息;案例3:emitTwo(handler,isFn,this,arguments[1],arguments[2]);休息;案例4:emitThree(handler,isFn,this,arguments[1],arguments[2],arguments[3]);休息;//较慢的默认值:args=newArray(len-1);对于(i=1;i