事件订阅机制

node.js中的异步回调,基本上是通过设置的监听器来触发的,这一个事件订阅机制普通存在于node.js环境中,从之前的学习,我们可以得知这其中的一个使用方法, :confused: 但是,我们是否有清楚了解过事件订阅机制的一个实现过程?除了基本的on/onceemit动作,是否有它自己的一个属性以及一些比较好用的api方法,值得我们深入学习与使用的呢? 答案是肯定的,在深入了解之前,我们先来看 :point_down: 的一个关于event的一个简单使用:

const { EventEmitter } = require('node:events');
const myEmitter = new EventEmitter();
myEmitter.on('xxx', content => {
    console.info('自定义事件接收到通知了,接收到的参数是:', content);
});
myEmitter.emit('xxx', '我是传递的参数!!');

:star2: 借助于设计模式中的订阅者模式,一般是有一个订阅者(这里刚好发布者也是订阅者),订阅者内部维护者事件监听器列表,当发布者发布事件时,从监听器列表中 捞出对应的事件监听器来执行!

:warning: 在未使用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');

events由于上下文捆绑导致this的指向不同 :stars: 从这里,可以看出由于箭头函数的挟持,导致this指向的区别,因此假如使用箭头函数的时候,需要利用闭包的机制,从外部拿到当前事件监听器的一个引用对象来使用!!!

:point_down: 下面来具体分析events中的事件机制,首先甩出一基本的机制过程图: events中的机制

这里配合源码来对这个过程进行一个详细的分析:

  1. 对象初始化:首先,events所提供的能够使用的对象以及方法只有:EventEmitter对象和once方法 events的入口 那么,这个里的EventEmitter对象是一个啥玩意呢? :point_right: 是一个普通的函数对象,一般是通过new方式来进行的构造调用的, 从new一个对象的过程中,完成了this的绑定与属性/方法的赋值,从EventEmitter构造函数调用的过程中,其实是调用的EventEmitter.init方法, 这里需要关注一下init方法的执行过程: EventEmitter对象初始化方法 :point_up_2: 从这里我们可以看出,初始化过程就是创建一个_events对象以及_eventsCount计数器,同时设置最大的监听器数量是默认的最大监听器数量;

  2. on的过程:完成创建一个事件对象后,接下来,我们通过on方法来设置监听器,其实on方法调用的是实例上的addListener方法,而addListener方法,则是调用 的_addListener(this, type, listener, prepend=false)方法,该方法中首先检查是否合规的监听器函数,然后对该对象在初始化时所创建的_events 进行的二次非空判断与同时创建初始化,如果设置了监听器newListener事件,则直接emit该事件(这里是为了防止js事件的递归调用,导致内存溢出),然后 将事件存入到当前对象中,以type=[listener]的方式来进行的存储( :point_up_2: 传递的pretend参数,主要是为了标识在发现的监听器之前还是之后,这将决定了对于同一个事件多个监听器的调用顺序), 设置完成后,将检查当前对象中所能允许的一个最大设置监听器数量,默认是10,设置为零则不限制(主要是因为一个对象若设置了太多的监听器对象的话,在该监听器回调的时候,将会占用太多的CPU执行时间,因为所有的异步回调都是同步的,而js又是单线程机制,这有可能会导致js执行卡住的问题), 假如设置超过了最大监听器数量,那么将由process.emit(error)来抛出警告,到此为止完成事件的存储在对象的_events中,具体设置监听器的过程如下: events中的addListener过程

  3. emit的过程:先判断是否是error监听事件,或者是未定义的error事件处理程序,从目标对象中的_events对象中捞出对应的事件来执行,若是取出来的值是一个数组的话,则克隆该数组并逐个执行(这里有可能是数组的原因是一个事件可以设置多个监听器的), 而设置多个监听器的方式可以是前追加或者是后追加,这取决于调用的方法!!

  4. once过程:其实是on的辩题,在on的基础上通过_onceWrap函数来包装的,而_onceWrap则是有onceWrap函数来执行并返回的包装函数,该函数onceWrap通过接收targettypelistenerfiredwrapFn来实现一包装函数,:point_right: 通过执行参数调用函数(this.target.listener(args)), 在这个target目标对象上追加一属性(fired=true),来实现回调函数只被包裹执行一次

:stars: 这里附带上完整的一个执行过程,其实就是简单的存储监听器,并对监听器进行取值后执行的动作

事件监听

  1. newListener: 所有设置on监听器,都会调用newListener事件,代表 :u6709: 是事件监听器设置在目标对象上了,可用于监听目标对象的监听器设置情况;
  2. removeListener: 在某个监听器从目标对象上被移除的时候,调用该方法

实例对象API

  1. 设置监听(onaddListenerprependListener)

    源码中onprependListener都指向的addListener事件,两者执行的同一个逻辑,从 :point_up: 的源码分析过程可以得知,设置的监听器不会检查其唯一性, 也就是允许对同一个事件设置多个事件监听器,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');
    

    events设置监听器的不同方式以及区别

:warning: 关于设置监听中还有两个方法 :point_right: once()prependOnceListener(),代表着仅触发一次,触发后立刻移除原本设置的监听器!!

  1. 移除监听器(removeListeneroffremoveAllListeners)

    这三者主要用于移除监听器,removeListeneroff一样,指向的同一个方法地址,而removeListListener则移除所有的监听器,这里 :u6709: 一个需要注意的是: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());
    

    查看与移除监听器

:stars: :point_up_2: 这里我们使用了emitter.eventNames()方法获取了当前目标对象上设置的监听器,而使用emitter.listeners('xxx')则可以获取某个事件名称被设置了多少个事件监听, 采用的removeAllListener('xxx')则可以移除掉不想使用的监听器!!

results matching ""

    No results matching ""