事件订阅机制
node.js中的异步回调,基本上是通过设置的监听器来触发的,这一个事件订阅机制普通存在于node.js环境中,从之前的学习,我们可以得知这其中的一个使用方法, 但是,我们是否有清楚了解过事件订阅机制的一个实现过程?除了基本的
on/once
和emit
动作,是否有它自己的一个属性以及一些比较好用的api方法,值得我们深入学习与使用的呢? 答案是肯定的,在深入了解之前,我们先来看 的一个关于event
的一个简单使用:
const { EventEmitter } = require('node:events');
const myEmitter = new EventEmitter();
myEmitter.on('xxx', content => {
console.info('自定义事件接收到通知了,接收到的参数是:', content);
});
myEmitter.emit('xxx', '我是传递的参数!!');
借助于设计模式中的订阅者模式,一般是有一个订阅者(这里刚好发布者也是订阅者),订阅者内部维护者事件监听器列表,当发布者发布事件时,从监听器列表中 捞出对应的事件监听器来执行!
在未使用ES6以后的箭头函数之前,回调函数中的this
一般指向的是emitter对象本身,而如果使用了箭头函数,那么这里的this就不在是箭头函数了,如下代码所示:
const { EventEmitter } = require('node:events');
const myEmitter = new EventEmitter();
myEmitter.on('xxx', function(xxx){
console.info(xxx, this);
})
myEmitter.on('xxx', xxx => {
console.debug(xxx, this);
})
myEmitter.emit('xxx', 'abc');
从这里,可以看出由于箭头函数的挟持,导致this指向的区别,因此假如使用箭头函数的时候,需要利用闭包的机制,从外部拿到当前事件监听器的一个引用对象来使用!!!
下面来具体分析events中的事件机制,首先甩出一基本的机制过程图:
这里配合源码来对这个过程进行一个详细的分析:
对象初始化:首先,events所提供的能够使用的对象以及方法只有:
EventEmitter
对象和once方法 那么,这个里的EventEmitter
对象是一个啥玩意呢? 是一个普通的函数对象,一般是通过new
方式来进行的构造调用的, 从new
一个对象的过程中,完成了this
的绑定与属性/方法的赋值,从EventEmitter构造函数调用的过程中,其实是调用的EventEmitter.init
方法, 这里需要关注一下init
方法的执行过程: 从这里我们可以看出,初始化过程就是创建一个_events对象
以及_eventsCount计数器
,同时设置最大的监听器数量是默认的最大监听器数量;on的过程:完成创建一个事件对象后,接下来,我们通过
on
方法来设置监听器,其实on方法调用的是实例上的addListener
方法,而addListener
方法,则是调用 的_addListener(this, type, listener, prepend=false)
方法,该方法中首先检查是否合规的监听器函数,然后对该对象在初始化时所创建的_events
进行的二次非空判断与同时创建初始化,如果设置了监听器newListener
事件,则直接emit该事件(这里是为了防止js事件的递归调用,导致内存溢出),然后 将事件存入到当前对象中,以type=[listener]
的方式来进行的存储( 传递的pretend参数,主要是为了标识在发现的监听器之前还是之后,这将决定了对于同一个事件多个监听器的调用顺序), 设置完成后,将检查当前对象中所能允许的一个最大设置监听器数量,默认是10,设置为零则不限制(主要是因为一个对象若设置了太多的监听器对象的话,在该监听器回调的时候,将会占用太多的CPU执行时间,因为所有的异步回调都是同步的,而js又是单线程机制,这有可能会导致js执行卡住的问题), 假如设置超过了最大监听器数量,那么将由process.emit(error)
来抛出警告,到此为止完成事件的存储在对象的_events
中,具体设置监听器的过程如下:emit的过程:先判断是否是error监听事件,或者是未定义的error事件处理程序,从目标对象中的
_events
对象中捞出对应的事件来执行,若是取出来的值是一个数组的话,则克隆该数组并逐个执行(这里有可能是数组的原因是一个事件可以设置多个监听器的), 而设置多个监听器的方式可以是前追加或者是后追加,这取决于调用的方法!!once过程:其实是on的辩题,在on的基础上通过
_onceWrap
函数来包装的,而_onceWrap
则是有onceWrap
函数来执行并返回的包装函数,该函数onceWrap
通过接收target
、type
、listener
、fired
、wrapFn
来实现一包装函数,:point_right: 通过执行参数调用函数(this.target.listener(args)
), 在这个target目标对象上追加一属性(fired=true),来实现回调函数只被包裹执行一次
这里附带上完整的一个执行过程,其实就是简单的存储监听器,并对监听器进行取值后执行的动作
事件监听
- newListener: 所有设置
on
监听器,都会调用newListener
事件,代表 是事件监听器设置在目标对象上了,可用于监听目标对象的监听器设置情况; - removeListener: 在某个监听器从目标对象上被移除的时候,调用该方法
实例对象API
- 设置监听(
on
与addListener
与prependListener
)源码中
on
与prependListener
都指向的addListener
事件,两者执行的同一个逻辑,从 的源码分析过程可以得知,设置的监听器不会检查其唯一性, 也就是允许对同一个事件设置多个事件监听器,on
是向后追加监听器,prependListener
是向前插入监听器,其最终都是往_events
对象中type的value值所对应的 数组中插入一个监听器函数对象,三者都是设置监听器,只是回调的先后顺序不一样而已,如下代码所示:const { EventEmitter } = require('node:events'); const myEmitter = new EventEmitter(); myEmitter.on('newListener', (event, listener) => { console.info('收到了newListener动作'); console.info(event, listener); }); myEmitter.on('xxx', () => { console.info('我是第一个xxx事件回调'); }); myEmitter.addListener('xxx', () => { console.info('我是第二个xxx事件回调'); }); myEmitter.prependListener('xxx', () => { console.info('我是最开始设置的xxx事件回调'); }); myEmitter.emit('xxx');
关于设置监听中还有两个方法 once()
与prependOnceListener()
,代表着仅触发一次,触发后立刻移除原本设置的监听器!!
- 移除监听器(
removeListener
、off
、removeAllListeners
)这三者主要用于移除监听器,
removeListener
与off
一样,指向的同一个方法地址,而removeListListener
则移除所有的监听器,这里 一个需要注意的是:newListener
可以说是系统级别的,我们无法移除它 如下代码所示:const { EventEmitter } = require('node:events'); const myEmitter = new EventEmitter(); myEmitter.on('newListener', (event, listener) => { console.info('收到了newListener动作'); console.info(event, listener); }); myEmitter.on('xxx', () => { console.info('我是第一个xxx事件回调'); }); myEmitter.addListener('xxx', () => { console.info('我是第二个xxx事件回调'); }); myEmitter.prependListener('xxx', () => { console.info('我是最开始设置的xxx事件回调'); }); myEmitter.emit('xxx'); console.info('当前目标监听器所设置的事件有:', myEmitter.eventNames()); console.info('xxx事件的监听器有:', myEmitter.listeners('xxx')); myEmitter.removeAllListeners('xxx'); console.info('当前目标监听器所设置的事件有:', myEmitter.eventNames());
这里我们使用了emitter.eventNames()
方法获取了当前目标对象上设置的监听器,而使用emitter.listeners('xxx')
则可以获取某个事件名称被设置了多少个事件监听,
采用的removeAllListener('xxx')则可以移除掉不想使用的监听器!!