-
Notifications
You must be signed in to change notification settings - Fork 226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
循序渐进教你实现一个完整的node的EventEmitter模块 #21
Comments
对观察者模式这一说法存疑,我的理解 EventEmitter 一边负责注册事件监听,一边负责触发事件,并没有监听到对象发生改变从而使得所有依赖它的对象得到通知,我理解有误吗 |
那你要看看其他监听者对象是不是监听的同一个被监听对象实例。 |
当你使用 比如 EventEmitter 并不关心谁发生了变化,他只负责注册和传递这些变化,但你不能否认 EventEmitter 为实现观察者模式而服务的。 |
不算是观察者模式吧。这就是一个订阅发布模式吧 |
循序渐进教你实现一个完整的node的EventEmitter模块
node的事件模块只包含了一个类:EventEmitter。这个类在node的内置模块和第三方模块中大量使用。EventEmitter本质上是一个观察者模式的实现,这种模式可以扩展node在多个进程或网络中运行。本文从node的EventEmitter的使用出发,循序渐进的实现一个完整的EventEmitter模块。
一、EventEmitter模块的基本用法和简单实现
(1) EventEmitter模块的基本用法
首先先了解一下EventEmitter模块的基本用法,EventEmitter本质上是一个观察者模式的实现,所谓观察者模式:
它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。
因此最基本的EventEmitter功能,包含了一个观察者和一个被监听的对象,对应的实现就是EventEmitter中的on和emit:
eventEmitter是EventEmitter模块的一个实例,eventEmitter的emit方法,发出say事件,通过eventEmitter的on方法监听,从而执行相应的函数。
(2) 简单实现一个EventEmitter模块
根据上述的例子,我们知道了EventEmitter模块的基础功能emit和on。下面我们实现一个包含emit和on方法的EventEmitter类。
on(eventName,callback)方法传入两个参数,一个是事件名(eventName),另一个是相应的回调函数,我们选择在on的时候针对事件名添加监听函数,用对象来包含所有事件。在这个对象中对象名表示事件名(eventName),而对象的值是一个数组,表示该事件名所对应的执行函数。
emit(eventName,...arg)方法传入的参数,第一个为事件名,其他参数事件对应的执行函数中的实参,emit方法的功能就是从事件对象中,寻找对应key为eventName的属性,执行该属性所对应的数组里面每一个执行函数。
下面来实现一个EventEmitter类
上述就实现了一个简单的EventEmitter类,下面来实例化:
二、node中常用的EventEmitter模块的API
跟在上述简单的EventEmitter模块不同,node的EventEmitter还包含了很多常用的API,我们一一来介绍几个实用的API.
除此之外,还有2个特殊的,不需要手动添加,node的EventEmitter模块自带的特殊事件:
上述node的EventEmitter的模块看起来很多很复杂,其实上述的API中包含了一些别名,仔细整理,理解其使用和实现不是很困难,下面一一对比和介绍上述的API。
(1) addListener和removeListener、on和off方法
addListener(eventName,listener)的作用是为指定事件添加一个监听器. 其别名为on
removeListener(eventName,listener)的作用是为移除某个事件的监听器. 其别名为off
再次需要强调的是:addListener的别名是on,removeListener的别名是off
接着我们来看具体的用法:
(2) removeListener和removeAllListeners
removeListener指的是移除一个指定事件的某一个监听器,而removeAllListeners指的是移除某一个指定事件的全部监听器。
这里举例一个removeAllListeners的例子:
(3) on和once方法
on和once的区别是:
on的方法对于某一指定事件添加的监听器可以持续不断的监听相应的事件,而once方法添加的监听器,监听一次后,就会被消除。
比如on方法(跟addListener相同):
也就是说on方法监听的事件,可以持续不断的被触发。
(4) 两个特殊的事件newListener和removeListener
我们知道当实例化EventEmitter模块之后,监听对象是一个对象,包含了所有的监听事件,而这两个特殊的方法就是针对监听事件的添加和移除的。
newListener:在添加新事件监听器触发
removeListener:在移除事件监听器时触发
以newListener为例,会在添加新事件监听器的时候触发:
从上述的例子来看,每当添加新的事件,都会自动的emit一个“newListener”事件,且参数为eventName(新事件的名)和listener(新事件的执行函数)。
同理特殊事件removeListener也是同样的,当事件被移除,会自动emit一个"removeListener"事件。
三、EventEmitter模块的异常处理
(1) node中的try catch异常处理方法
在node中也可以通过try catch方式来捕获和处理异常,比如:
上述let x=x 赋值语句的错误会被捕获。这里提异常处理,那么跟事件有什么关系呢?
node中有一个特殊的事件error,如果异常没有被捕获,就会触发process的uncaughtException事件抛出,如果你没有注册该事件的监听器(即该事件没有被处理),则 Node.js 会在控制台打印该异常的堆栈信息,并结束进程。
比如:
在上述代码中没有监听error的事件函数,因此会触发process的uncaughtException事件,从而打印异常堆栈信息,并结束进程。
对于阻塞或者说非异步的异常捕获,try catch是没有问题的,但是:
try catch不能捕获非阻塞或者异步函数里面的异常。
举例来说:
上述代码中,以为try方法里面是同步的,因此可以捕获异常。如果try方法里面有异步的函数:
因为process.nextTick是异步的,因此在process.nextTick内部的错误不能被捕获,也就是说try catch不能捕获非阻塞函数内的异常。
(2) 通过domains管理异常
node中domain模块能被用来集中地处理多个异常操作,通过node的domain模块可以捕获非阻塞函数内的异常。
同样的,即使process.nextTick是一个异步函数,domain.on方法也可以捕获这个异步函数中的异常。
即使更复杂的情况下,比如异步嵌套异步的情况下,domain.on方法也可以捕获异常。
在上述的情况下,即使异步嵌套很复杂,也能在最外层捕获到异常。
(3) domain模块缺陷
在node最新的文档中,domain被废除(被标记为:Deprecated),domain从诞生之日起就有着缺陷,举例来说:
如上述的代码是无法捕获到异常Error的,原因在于发出异常的EventEmitter实例e,以及触发异常的定时函数timer没有被domain包裹。domain模块是通过重写事件循环中的nextTick和_tickCallback来事件将process.domain注入到next包裹的所有异步事件内。
解决上述无法捕获异常的情况,只需要将e或者timer包裹进domain。
就可以成功的捕获异常。
domain模块已经在node最新的文档中被废除
(4)process.on('uncaughtException')的方法捕获异常
node中提供了一个最外层的兜底的捕获异常的方法。非阻塞或者异步函数中的异常都会抛出到最外层,如果异常没有被捕获,那么会暴露出来,被最外层的process.on('uncaughtException')所捕获。
这样就能在最外层捕获异步或者说非阻塞函数中的异常。
四、完整的实现一个EventEmitter模块(可选读)
在第二节中我们知道了EventEmitter模块的基本用法,那么根据基本的API我们可以进一步自己去实现一个EventEmitter模块。
每一个EventEmitter实例都有一个包含所有事件的对象_events,
事件的监听和监听事件的触发,以及监听事件的移除等都在这个对象_events的基础上实现。
(1) emit
emit的方法实现的大致功能如下程序流程图所示:
从上述的程序图出发,我们开始实现自己的EventEmitter模块。
首先生成一个EventEmitter类,在类的初始化方法中生成这个事件对象_events.
_eventsCount用于统计事件的个数,也就是_events对象有多少个属性。
接着我们来实现emit方法,根据框图,我们知道emit所做的事情是在_events对象中取出相应type的属性,并执行属性所对应的函数,我们来实现这个emit方法。
(2) on、addListener和prependListener方法
emit方法是出发事件,并执行相应的方法,而on方法则是对于指定的事件添加监听函数。用程序来说,就是往事件对象中_events添加相应的属性.程序流程图如下所示:
接着我们来实现这个方法:
在on方法的基础上可以实现addListener方法和prependListener方法。
addListener方法是on方法的别名:
prependListener方法相当于在头部添加,指定prepend为true:
(3) removeListener和removeAllListeners
接着来看移除事件监听的方法removeListener和removeAllListeners,下面我们来看removeListener的程序流程图:
接着来看removeListener的代码:
最后来看removeAllListener,这个与removeListener相似,只要找到传入的type所对应属性的值,没有遍历过程,直接删除这个属性即可。
除此之外,还有其他的类似与once、setMaxListeners、listeners也可以在此基础上实现,就不一一举例。
五、总结
本文从node的EventEmitter模块的使用出发,介绍了EventEmitter提供的常用API,然后介绍了node中基于EventEmitter的异常处理,最后自己实现了一个较为完整的EventEmitter模块。
The text was updated successfully, but these errors were encountered: