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

EventEmitter源码分析及简单实现

时间:2023-04-03 23:51:41 Node.js

阅读原文EventEmitter简介EventEmitter是NodeJS核心模块events中的一个类,用于统一管理NodeJS中的事件,并使用事件特定的API来添加、触发以及移除事件等等,核心方法的模式类似于发布-订阅。EventEmitter1、EventEmitter构造函数的实现//文件:events.jsfunctionEventEmitter(){this._events=Object.create(null);}/**其他方法*///导出自定义模块module.export=EventEmitter;构造函数EventEmitter上有一个属性_events,类型为object,用于存储和统一管理所有类型的事件。创建构造函数时,会导出EventEmitter,实现其他方法的代码将放在构造函数和导出之间。2.最大事件监听数EventEmitter中监听的每类事件都有最大监听数。如果超过这个值,事件可以正常执行,但是会发出警告信息,防止内存泄漏。//默认最大事件监听数EventEmitter.defaultMaxListeners=10;默认同类型事件最大监听数为10,EventEmitter当然也有设置和获取这个值的方法,下面是设置和获取同类型事件最大监听数的方法。//操作最大事件监听数//设置最大同类型事件监听数EventEmitter.prototype.setMaxListeners=function(count){this._count=count;}//获取最大事件监听数同类型的EventEmitter.prototype。getMaxListeners=function(){返回this._count||EventEmitter.defaultMaxListeners;}设置这个值的时候,其实是在EventEmitter实例中增加了一个_count属性,用来存储设置为该类事件最大监听的新值,获取的时候需要获取_count个数,获取默认的如果未设置值。3、添加事件监听在EventEmitter实例中添加事件监听时,事件的类型会作为_event对象中的属性名,值为数组。每次添加该类型的事件时,将要执行的功能存储在该数组中统一管理。添加事件监听器有on、once、addListener、prependListener、prependOnceListener方法:on相当于addListener,正常将函数添加到_event的事件类型对应的数组中;一次将函数添加到_event的事件类型对应的数组中,但只能执行一次;prependListener将函数添加到_event的事件类型对应的数组前面;prependOnceListener将函数添加到_event对应事件类型数组的最前面,但只能执行一次。在EventEmitter中正常添加事件需要注意四点:1、如果其他类使用util模块的inherits方法继承EventEmitter,则不能继承实例属性。在调用_events的方法中,因为获取不到_events而报错,为了兼容这种继承,当无法获取到_events时,应该在继承EventEmitter的类的实例中添加一个_events;2.如果添加的事件的类型是newListener,要执行的函数会有一个参数type,是事件的类型。后面添加事件时,会执行newListener函数,处理添加事件的事件类型;3、on方法表面上有两个参数,实际上还有第三个参数,它是一个布尔值,代表是否从_events事件类型对应的数组前面添加函数成员;4、添加事件时,需要判断是否超过了该类事件的最大监听数,如果超过了,会打印警告信息。on方法和addListener方法的实现://on和addListener方法//添加事件监听器EventEmitter.prototype.on=EventEmitter.prototype.addListener=function(type,callback,flag){//不存在兼容继承的情况_eventsif(!this._events)this._events=Object.create(null);//如果类型不是newListener,则执行newListener的回调if(type!=="newListener"){//如果没有添加newListener事件,则忽略这里的逻辑if(this._events["newListener"]&&this._events["newListener"].length){this._events["newListener"].forEach(fn=>fn(type));}}//如果不是第一次添加callback并存入数组if(this._events[type]){//是否从数组最前面添加callbackif(flag){this._events[类型].unshift(回调);}else{this._events[type].push(callback);}}else{//第一次添加,在_events中创建一个数组,并在数组中添加callbackthis._events[type]=[callback];}//获取最大事件监听数letmaxListeners=this.getMaxListeners();//判断类型type的事件是否超过最大监听数,超过打印警告信息if(this._events[type].length-1===maxListeners){console.error(`MaxListenersExceededWarning:${maxListeners+1}${type}listenersadded`);通过上面的代码可以看出on方法的第三个参数其实是服务于prependListener方法的。其他添加事件的方法都是基于on实现的,只是在调用on的外层做了不同的处理,而我们通常调用这些添加的事件监听器,在使用方法时,只传入type和callbackprependListener方法的实现://prependListener方法//添加事件监听,添加EventEmitter.prototype.prependListener=function(type,callback){//第三个参数为true表示事件类型对应的数组从_events前面添加回调this.on(type,callback,true);}once方法的实现://once方法//添加事件监听,只执行一次EventEmitter.prototype.once=function(type,callback,flag){letwrap=>(...args){回调(...args);//执行回调后立即从数组中删除回调this.removeListener(type,wrap);}//存储回调,一定要单独使用removeListener删除传入的回调可以删除wrap.realCallback=callback;//调用添加事件监听this.on(type,wrap,flag);}如果想让事件只执行一次,需要在执行完回调后马上把数组中的这个函数去掉,因为是同步执行的,很难直接操作回调。添加事件其实就是给对应类型_events的数组添加回调。当我们使用一次的时候,我们用一个叫wrap的函数来包装回调,把这个外层的函数存入一个数组,wrap的内部逻辑就是真正的回调的调用和wrap的移除。这里,事件监听器remove方法removeListener,后面会详细介绍。once的第三个参数是为prependOnceListener服务的,prependOnceListener和prependListener类似,不同的是prependOnceListener是基于once实现的。prependOnceListener方法的实现://prependOnceListener方法//添加事件监听器,从数组最前面追加,只执行一次EventEmitter.prototype.prependOnceListener=function(type,callback){//第三个参数为true表示来自_eventsAddcallbackthis.once(type,callback,true);}4.移除事件监听器移除事件监听器有两种方法,分别是removeListener和removeAllListeners,前者的作用是移除某个类型数组的回调函数中,后者的作用是移除某个类型数组的所有成员,如果类型参数为空,则清空整个_events。removeListener方法的实现://removeListener方法//移除事件执行器EventEmitter.prototype.removeListener=function(type,callback){if(this._events[type]){//过滤掉当前传入的要移除的回调this._events[type]=this._events[type].filter(fn=>{returnfn!==callback&&fn!==callback.realCallback;});}}由于一旦在真正的回调包中加了一层wrap,wrap和removeListenerdelete函数只能在事件触发时执行。如果在事件触发前使用removeListener删除,传入的是真正的callback回调,无法删除,所以once方法中真正的回调是在removeListener中调用filter时返回条件的逻辑中存储和处理的.removeAllListeners方法的实现://removeAllListeners方法//移除所有事件执行器EventEmitter.prototype.removeAllListeners=function(type){//有type清除_events对应的数组,否则直接清除_eventsif(type){这个._events[type]=[];}else{this._events=Object.create(null);}}5.触发事件监听执行一个事件比较简单。取出_events中对应类型的数组并循环,执行内部for中的每一个函数,第一个参数为type,后面的参数将作为数组中函数执行时传入的参数。//emit方法//触发事件EventEmitter.prototype.emit=function(type,...args){if(this._events[type]){//循环执行函数,并将this返回给EventEmitter实例this._events[type].forEach(fn=>fn.call(this,...args));}}6.获取事件类型名称集合//eventNames方法//获取所有监听的事件类型EventEmitter.prototype.eventNames=function(){returnObject.keys(this._events);}7.通过事件类型获取executor的集合//listenersmethod//获取事件类型对应的数组EventEmitter.prototype.listeners=function(type){returnthis._events[type];}EventEmitter的基本使用EventEmitter的核心逻辑已经实现。由于以上方法大多需要结合使用,就不一一验证了。让我们通过一些案例来了解EventEmitter的用法。我们这里引入了自己的自定义事件模块,使用util模块的继承来继承EventEmitter。以下为前置代码,后面不再赘述。//文件:events-demo.js//引入依赖constEventEmitter=require("./events");constutil=require("util");functionGirl(){}//让Girl继承EventEmitterutil.inherits(Girl,EventEmitter);//创建一个Girl实例letgirl=newGirl();案例一:设置并获取同类型事件的最大监听数//文件:events-demo.js//获取事件的最大监听数console.log(girl.getMaxListeners());//10//设置最大事件监听数girl.setMaxListeners(2);console.log(girl.getMaxListeners());//2案例2:使用on添加事件并执行//文件:events-demo.jsgirl.on("brokenlove",()=>console.log("crying"));girl.on("失恋",()=>console.log("喝酒"));girl.emit("失恋");//哭//喝酒案例3:使用prependListener添加事件并执行//文件:events-demo.jsgirl.on("brokenlove",()=>console.log("Crying"));girl.prependListener("BrokenLove",()=>console.log("Drinking"));girl.emit("BrokenLove");//drinking//cryingcase4:添加newListener类型的事件//file:events-demo.jsgirl.on("newListener",(type)=>console.log(type));girl.on("失恋",()=>console.log("哭"));girl.on("和好",()=>console.log("幸福"));//失恋//协调案例5:添加同类型事件超过最大数量并执行该事件//文件:events-demo.js//设置事件gir的最大监听数量l.setMaxListeners(2);girl.on("Lovebreak",()=>console.log("Crying"));girl.on("Lovebreak",()=>console.log("Drinking"));girl.on("Lovebreak",()=>console.log("Smoking"));girl.emit("Lovebreak");//MaxListenersExceededWarning:3Lovebreaklistenersadded//Crying//Drinking//Smokingcase6:on和once对比//文件:events-demo.jsgirl.on("brokenlove",()=>console.log("crying"));girl.once("失恋",()=>console.log("Drinking"));girl.emit("Lovebreak");girl.emit("Lovebreak");//哭//喝酒//哭案例7:移除并添加一次事件监听器//文件:events-demo.jsletcry=()=>console.log("crying");letdrink=()=>console.log("drinking");girl.on("失恋",哭);girl.once("失恋",喝);girl.on("失恋",()=>console.log("抽烟"));girl.removeListener("失恋”,哭泣);女孩。removeListener("brokenlove",drink);//吸烟案例8:使用prependOnceListener添加事件监听//文件:events-demo.jsgirl.on("brokenlove",()=>console.log("cry"));girl.prependOnceListener("Lovebreak",()=>console.log("Drinking"));girl.emit("Lovebreak");girl.emit("Lovebreak");//喝//哭//CryCase9:获取某个事件类型的执行者集合//文件:events-demo.jsletcry=()=>console.log("cry");letdrink=()=>console.log("喝酒");girl.on("失恋",哭);girl.once("失恋",喝酒);girl.once("失恋",()=>console.log("smoking"));console.log(girl.listeners("brokenlove"));//[[功能:哭],[功能:喝],[功能]]案例10:获取所有事件类型名称//文件:events-demo.jsgirl.on("Breakdown",()=>console.log("Crying"));girl.on("和好",()=>console.log("开心"));console.log(girl.eventNames());//['失恋','和好']案例11:使用removeAllListeners按类型移除事件监听器log("Drinking"));girl.on("Reconcile",()=>console.log("Happy"));//移除事件监听girl.removeAllListeners("Lovebreak");console.log(girl.listeners("Lovebreak"));//[]Case12:使用removeAllListeners移除所有事件监听//File:events-demo.jsgirl.on("brokenlove",()=>console.log("哭泣"))??;girl.on("失恋",()=>console.log("喝酒"));girl.on("和好",()=>console.log("开心"));//移除所有事件听众girl.removeAllListeners();console.log(girl._events);//{}EventEmitter总结events模块在NodeJS中使用率非常高,很多其他模块的事件执行机制都是继承自该模块的EventEmitter类,比如ReadStream(可读流)、WriteStream(可写流)、net(tcp)和http等,我们也可以通过上面的案例创建自己的类继承EventEmitter实现事件管理