详解如何模拟实现node中的Events模块(通俗易懂版)

作者:arzh 时间:2024-05-05 09:20:55 

Nodejs 的大部分核心 API 都是基于异步事件驱动设计的,事件驱动核心是通过 node 中 Events 对象来实现事件的发送和监听回调绑定,我们常用的 stream 模块也是依赖于 Events 模块是来实现数据流之间的回调通知,如在数据到来时触发 data 事件,流对象为可读状态触发 readable 事件,当数据读写完毕后发送 end 事件。

既然 Events 模块如此重要,我们有必要来学习一下 Events 模块的基本使用,以及如何模拟实现 Events 模块中常用的 api

一、Events 模块的基本使用以及简单实现

首先我们了解一下 Events 模块的基本用法,其实 Events 模块本质上是观察者模式的实现,所谓观察者模式就是:

它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知

观察者模式有对应的观察者以及被观察的对象,在 Events 模块中,对应的实现就是 on 和 emit 函数


const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('嗨', (str) => {
console.log(str);
});
myEmitter.emit('嗨','你好');

从上述的使用中,我们可以知道 on 是用来监听事件的发生,而 emit 是用来触发事件的发生,一旦 emit 触发了事件,on 就会被通知到,从而执行对应的回调函数。

有了这个实例,我们可以思考下如何实现这个 EventEmitter 类。

思路:当我们执行 on 函数时,我们可以将回调函数保存起来,等到 emit 触发了事件时,将回调函数拿出来执行,那么就可以实现了事件的监听以及订阅了。


class EventEmitter{
constructor(){
 #事件监听函数保存的地方
 this.events={};
}
on(eventName,listener){
 if (this.events[eventName]) {
  this.events[eventName].push(listener);
 } else {
  #如果没有保存过,将回调函数保存为数组
  this.events[eventName] = [listener];
 }
}
emit(eventName){
 #emit触发事件,把回调函数拉出来执行
 this.events[eventName] && this.events[eventName].forEach(listener => listener())
}
}

上述就实现了一个简单的 EventEmitter 类,下面来实例一下:


let event = new EventEmitter();
event.on('嗨',function(){
console.log('你好');
});
event.emit('嗨');
#输出:你好

完善:我们注意到在原生的 EventEmitter 类中,emit 是可以传递参数到我们的回调函数中,那么我们实现的类也应该支持传递参数。我们对 emit 进行如下更改


emit(eventName,...rest){
#emit触发事件,把回调函数拉出来执行
this.events[eventName] && this.events[eventName].forEach(listener => listener.apply(this,rest))
}

完善之后,重新实例化,如下:


let event = new EventEmitter();
event.on('嗨',function(str){
console.log(str);
});
event.emit('嗨','你好');
#输出:你好

二、Events 模块中常用的 api

Events 模块中除了 on、emit 函数之外,还包含了很多常用的 api,我们一一来介绍几个实用的 api

API名称API方法描述
addListener(eventName, listener)on(eventName, listener)别名,为指定事件添加一个 * 到 * 数组的尾部
removeListener(eventName, listener)从名为 eventName 的事件的 * 数组中移除指定的 listener
removeAllListeners(eventName, listener)移除全部 * 或指定的 eventName 事件的 *
once(eventName, listener)添加单次 * listener 到名为 eventName 的事件
listeners(eventName)返回名为 eventName 的事件的 * 数组的副本
setMaxListeners(n)可以为指定的 EventEmitter 实例修改 * 数量限制

1. addListener 与 on 方法使用与实现

在 Events 模块中,addListener 与 on 方法的使用是完成相同的,只是名字不同,我们可以通过原型来给两个函数建立相等关联


EventEmitter.prototype.addListener=EventEmitter.prototype.on

2. removeListener 与 off 方法使用与实现

removeListener 方法可以从指定名字的 * 数组中移除指定的 listener,这样的话,当再次 emit 事件的时候,不会触发 on 绑定的回调函数,如下:


const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
let callback = (str) => {
console.log(str);
}
myEmitter.on('嗨', callback);
myEmitter.emit('嗨','你好');#输出:你好
myEmitter.removeListener('嗨',callback);
myEmitter.emit('嗨','你好');#无输出

实现思路:我们只要在执行 removeListener 函数的时候,将先前保存的回调函数去除掉即可


removeListener (eventName,listener) {
#保证回调函数数组存在,同时去除指定的listener
this.events[eventName] && this.events[eventName] = this.events[eventName].filter(l => l != listener);
}

同时 removeListener 与 off 方法也是功能完全相同,只是命名不同,因此可以通过如下方法赋值:


EventEmitter.prototype.removeListener=EventEmitter.prototype.off

3. removeAllListeners 方法使用与实现

removeAllListeners 移除全部 * 或指定的 eventName 事件的 * ,其实 removeAllListeners 就包含了 removeListener 的功能,只是 removeListener 只能指定特定的 * ,removeAllListeners 可以移除全部 * 。

实现思路:在执行 removeAllListeners,将所有的回调函数都给去除即可


removeAllListeners (eventName) {
#移除全部 *
delete this.events[eventName]
}

4. once 方法使用与实现

once 方法的描述是添加单次 * listener 到名为 eventName 的事件,其实就是通过 once 添加的 * ,只能执行一次,执行一次之后就会被销毁,不能再次执行


const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.once('嗨', (str) => {
console.log(str);
});
myEmitter.emit('嗨','你好');
myEmitter.emit('嗨','你好');
myEmitter.emit('嗨','你好'); #只能输出一次你好

实现思路:当 once 监听的事件回调函数执行之后,通过 removeListener 将事件 * 给解绑掉,那么事件再次被 emit 的时候,就不会再次执行回调,这样就能保证事件回调只能执行一次


once (eventName, listener) {
#重新改变监听回调函数,使其执行之后可以被销毁
let reListener = (...rest) => {
 listener.apply(this,rest);
 #执行完之后解除事件绑定
 this.removeListener(type,wrapper);
}
this.on(eventName,reListener);
}

5. listeners 方法使用与实现

listeners 方法返回名为 eventName 的事件的 * 数组的副本,其实就是获取 eventName 中所有的回调函数,这个实现起来很容易,就不多赘述了,代码如下:


listeners (eventName) {
return this.events[eventName]
}

6. setMaxListeners 方法使用与实现

默认情况下,如果为特定事件添加了超过 10 个 * ,则 EventEmitter 会打印一个警告。 这有助于发现内存泄露, 但是,并不是所有的事件都要限制 10 个 * 。 emitter.setMaxListeners() 方法可以为指定的 EventEmitter 实例修改限制。 值设为 Infinity(或 0)表示不限制 * 的数量。


const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
let callback = (str) => {
console.log(str);
}
for (let i = 0; i <= 11; i++) {
myEmitter.on('嗨', callback);
}
myEmitter.emit('嗨', '你好');

输入结果如图:

详解如何模拟实现node中的Events模块(通俗易懂版)

实现思路:

我们先将特定事件的 * 最大设置为常量10


constructor(){
#事件监听函数保存的地方
this.events={};
#最大 * 数量
this._maxListeners = 10;
}

然后在我们的 on 函数中,对这个 * 的数量进行判断,从而作出提示


on(eventName,listener){
if (this.events[eventName]) {
 this.events[eventName].push(listener);
 #如果超过最大限度,以及不为0,则作出内存泄漏提示
 if (this._maxListeners != 0 && this.events[type].length >= this._maxListeners) {
  console.error('超过最大的监听数量可能会导致内存泄漏');
 }
} else {
 #如果没有保存过,将回调函数保存为数组
 this.events[eventName] = [listener];
}
}

我们也支持对 _maxListeners 变量根据用户的输入进行更改,即我们的 setMaxListeners() 函数


setMaxListeners(MaxListeners) {
this._maxListeners = MaxListeners
}

三、总结

本文从 node 的 Events 模块出发,然后去介绍了 Events 模块常用 API 的使用,从中通过一步一步简易去思考这些 API 使用的内部原理,简易的实现了这些 API,希望大家看完文章之后,能对 Events 模块有进一步的理解。

来源:https://juejin.im/post/5cae9213f265da038a146108

标签:node,Events模块
0
投稿

猜你喜欢

  • 细线表格的处理

    2008-08-06 12:53:00
  • 在登录触发器错误情况下连接SQL Server的方法

    2024-01-25 19:37:51
  • 在asp中使用存储过程

    2008-02-26 12:17:00
  • Python 中的Sympy详细使用

    2021-10-03 03:22:45
  • 利用Python学习RabbitMQ消息队列

    2021-01-08 21:06:09
  • python3.5+tesseract+adb实现西瓜视频或头脑王者辅助答题

    2022-06-13 04:36:44
  • 如何在Python中用好短路机制

    2022-04-23 16:56:42
  • Mysql树形递归查询的实现方法

    2024-01-14 08:05:16
  • python 获取一个值在某个区间的指定倍数的值方法

    2023-04-08 17:19:07
  • python opencv捕获摄像头并显示内容的实现

    2021-12-08 23:16:24
  • Python Web App开发Dockerfiles编写示例

    2023-02-01 12:47:19
  • Python编程之微信推送模板消息功能示例

    2022-11-15 03:45:04
  • 爱你就要说出来,来表白吧

    2008-11-23 16:23:00
  • JavaScript中判断的优雅写法示例

    2024-04-10 10:43:46
  • Python简单网络编程示例【客户端与服务端】

    2023-12-07 10:26:55
  • python异常处理之try finally不报错的原因

    2023-05-01 00:02:40
  • Python命令启动Web服务器实例详解

    2022-10-09 11:53:42
  • js中string转int把String类型转化成int类型

    2024-05-03 15:30:11
  • win10上如何安装mysql5.7.16(解压缩版)

    2024-01-23 23:22:53
  • setInterval 和 setTimeout 会产生内存溢出

    2008-03-08 13:10:00
  • asp之家 网络编程 m.aspxhome.com