🎶🎶Node.js 内置模块大揭秘:事件工具 (events) 模块解析

前言

大家好,今天是我们《Node.js 内置模块大揭秘》这个系列的第三篇文章,本文将深入探讨 Node.js 的 events 模块,涵盖其基本用法、关键特性以及在实际应用中的应用场景。

其他模块可从下面了解

第一篇 Node.js 内置模块大揭秘:文件系统(fs)模块(含fs模块常见函数总结) - 掘金 (juejin.cn)

第二篇 Node.js 内置模块大揭秘:超文本传输协议(http)模块 - 掘金 (juejin.cn)

这篇文章中我们会涉及:

  • 事件驱动编程
  • Node.js 的事件循环
  • 事件循环与非阻塞 I/O 的关系
  • Events 模块的基础使用
  • 高级事件处理与模式
  • Events 模块在 HTTP 模块和 FS 模块的应用场景

第一部分:Node.js 事件驱动模型的基础

1.1 事件驱动编程的概念

1.1.1 什么是事件驱动编程

事件驱动编程(Event-Driven Programming)是一种编程范式,在这种模型中,程序不是按照线性的控制流程执行,而是通过事件的触发和响应来驱动执行。

通俗一点来说,想象一下你正在开车,车辆的行驶过程就是一个事件,你的驾驶操作(例如刹车,转向)就是事件的触发,车辆的反应(例如刹车灯亮起,转向灯亮起)就是事件的处理。在这个过程中,我们并不需要等待某个步骤的完成,而是根据需要随时采取行动,这就是事件驱动的思想。

1.1.2 为什么事件驱动在异步环境中非常有效

事件驱动编程在异步环境中非常有效的原因主要是它与异步处理和实时响应的特性相辅相成。

  1. 非阻塞操作: 异步环境中,某些操作(例如文件读写、网络请求等)可能会花费较长时间。在事件驱动模型中,当执行一个潜在耗时的操作时,程序不会等待其完成,而是继续执行其他任务。只有在操作完成时,相应的事件被触发,事件处理器才会被调用。这样可以避免程序在等待耗时操作完成时被阻塞。
  2. 实时响应: 异步环境下,事件驱动模型能够实现实时响应。当某个事件发生时,程序能够立即采取相应的行动,而不需要等待其他操作的完成。这对于需要快速交互和实时更新的应用场景非常重要,例如图形用户界面(GUI)、网络通信等。
  3. 高并发处理: 在异步环境中,多个事件可以并发地发生和处理。事件驱动模型使得程序能够有效地处理大量的并发操作,而不会因为等待某个操作的完成而导致整个程序的性能下降。

想象一下我们要做家务,现在有洗碗,擦桌子,扫地这几件事情,在同步模式下,我们可能会按照一定的顺序,比如先洗碗,再擦桌子,最后扫地。这就像是程序按照固定的步骤一步一步执行。

现在,假设你是一个事件驱动的家务处理者。每次有一个任务完成,你就会发布一个"任务完成"的事件。其他家庭成员(也就是事件处理器)都在等待这些事件。这样,你可以在洗碗的同时,家庭其他成员可以擦桌子、扫地,不用等你洗完碗再轮到他们。这就是事件驱动模型的效果,大家可以并发地完成任务,不需要一个等一个。

这种方式就能更高效地利用时间,提高整个程序的响应速度。所以,事件驱动在异步环境中非常有效,就好比家务处理中大家可以并发地完成各自的任务一样。

1.2 Node.js 的事件循环:

1.2.1 Node.js 的事件循环是如何工作的

我们都知道浏览器有事件循环机制,但是实际面试中还有另外一个常驻考题, Node.js 里的事件循环。

1) 按电梯例子

想象一下你在等电梯的场景。你按下电梯按钮后,电梯并不会立刻到达,而是会在等待区域持续检查有没有人按下楼层按钮。当有人按下按钮时,电梯就会响应,去执行对应的任务(比如到达指定楼层)。

在这个场景中:

  1. 你按下按钮相当于触发了一个事件。
  2. 电梯在等待区域检查按钮相当于事件循环的轮询阶段。
  3. 电梯到达指定楼层相当于执行了事件对应的回调函数。

在 Node.js 中,事件循环也是类似的:

  1. 代码执行中可能会触发一些事件,比如读取文件完成、接收到网络请求等。
  2. 事件循环会不断地检查有没有这些事件发生。
  3. 当事件发生时,执行相应的回调函数,继续执行其他任务。

这种事件驱动的模型使得 Node.js 能够在执行异步(不等待某些操作完成)的同时高效处理大量的并发请求,就像电梯在等待区域不断地检查按钮一样。这样,Node.js 能够在等待一些任务完成的同时继续执行其他任务,提高了程序的性能和响应速度。

2) 餐厅点餐例子

如果按电梯按钮的例子不足以让我们理解,我们再举一个例子:

假设一个餐厅的场景,其中服务员是一个事件循环。顾客在餐厅里进入,点菜,等待上菜,然后付款离开。在这个例子中:

  1. 顾客点菜(触发事件): 顾客进入餐厅,点了一份菜。这就像在程序中触发了一个事件。

  2. 服务员轮询(事件循环的轮询阶段): 服务员不是一直在等待着某个顾客完成用餐。相反,服务员会不断地巡视各张桌子,查看是否有顾客点了菜(发生了事件)。

  3. 服务员执行任务(执行回调函数): 当服务员发现有桌子的顾客点了菜,服务员就会去菜厨房取菜,并将菜端到顾客桌上。这就像执行了与点菜事件相关联的回调函数。

  4. 顾客等待上菜(程序中执行其他任务): 在服务员取菜的时候,其他顾客也可以点菜,服务员会继续巡视其他桌子,不会因为一个桌子的任务而停滞。

在这种模型下,服务员(事件循环)能够高效地处理多个桌子(事件)的点菜和上菜,而不需要等待一个桌子的任务完成再处理下一个桌子。这种异步的方式有助于提高效率,类似于 Node.js 处理异步任务的方式。

1.2.2 事件循环与非阻塞 I/O 的关系

事件循环(Event Loop)和非阻塞 I/O 是两个在异步编程中经常涉及的概念。它们通常被用于解释 JavaScript(尤其是在浏览器端)和其他语言(如Node.js)中的异步执行模型。下面是它们之间的关系:

  1. 事件循环(Event Loop)

    • 事件循环是一种程序结构,用于处理和分发程序中发生的事件。
    • 在Web开发中,事件循环通常与浏览器环境一起使用,负责处理用户交互、定时器、网络请求等异步事件。
    • 它通过一个循环不断地检查事件队列(Event Queue)中是否有待处理的事件。
    • 当事件循环发现事件队列中有事件,它会将事件的回调函数加入执行队列,然后执行这些回调函数。
  2. 非阻塞 I/O

    • 非阻塞 I/O 是指在进行输入输出操作时,程序不会被阻塞等待操作完成,而是可以继续执行其他任务。
    • 这通常涉及到异步编程模型,其中 I/O 操作被发起后,程序可以继续执行而无需等待 I/O 操作完成。
    • 因此,非阻塞 I/O 可以提高程序的并发性和响应性,尤其在处理大量并行操作时。
  3. 关系

    • 在事件驱动的环境中,比如浏览器环境或Node.js,事件循环是用于管理异步任务的主要机制。
    • 非阻塞 I/O 可以被看作是事件循环机制的一种实现方式。当进行异步的 I/O 操作时,事件循环允许程序继续执行其他任务,而不必等待 I/O 完成。
    • 在事件循环中,I/O 完成时,相应的回调函数会被添加到执行队列,然后在事件循环的下一轮执行。

综上所述,事件循环和非阻塞 I/O 在异步编程中协同工作,使得程序能够更高效地处理并发任务,同时保持响应性。在现代的Web开发中,这种机制对于处理大量并发请求、用户交互和网络操作非常重要。

第二部分:Events 模块的基础使用

2.1 引入 Events 模块

2.1.1 require('events') 的作用

要开始使用 events 模块,首先需要引入它。在 Node.js 中,可以使用 require 语句:

javascript 复制代码
const EventEmitter = require('events');

2.1.2 创建事件发射器的实例

接下来,创建一个事件发射器实例:

javascript 复制代码
const myEmitter = new EventEmitter();

现在,myEmitter 就是一个事件发射器的实例,我们可以使用它来注册监听器、触发事件等。

2.2 监听和触发事件

2.2.1 on 方法

on 方法可以用于注册事件监听器。我们可以利用它来监听特定事件,当该事件被触发时执行回调函数。

javascript 复制代码
myEmitter.on('event', (arg) => {
  console.log('事件被触发,参数为:', arg);
});

2.2.2 emit 方法的使用

使用 emit 方法触发特定的事件,并传递参数给事件监听器:

javascript 复制代码
myEmitter.emit('event', '这是传递的参数');

2.2.3 多个监听器的执行顺序

多个监听器按照它们被添加的顺序执行。示例代码:

javascript 复制代码
myEmitter.on('event', () => {
  console.log('第一个监听器执行');
});

myEmitter.on('event', () => {
  console.log('第二个监听器执行');
});

myEmitter.emit('event');

在这个例子中,第一个监听器会在第二个监听器之前执行。

注意: 这里的events监听和触发事件和普通监听事件有什么区别呢?

Events 模块提供的事件监听和触发机制是基于异步事件驱动的方式,通过 EventEmitter 实例进行注册和触发,支持多个监听器;而普通监听通常是同步的,通过直接调用回调函数,一般适用于简单的同步操作。

第三部分:高级事件处理与模式

3.1 自定义事件:

3.1.1 如何创建和触发自定义事件

通过继承 EventEmitter 类,我们可以创建自定义事件。示例:

javascript 复制代码
// 创建自定义事件发射器类 MyEmitter,继承自 EventEmitter
class MyEmitter extends EventEmitter {}

// 创建 MyEmitter 的实例 myCustomEmitter
const myCustomEmitter = new MyEmitter();

// 注册自定义事件监听器,当 'customEvent' 事件触发时执行回调
myCustomEmitter.on('customEvent', () => {
  console.log('自定义事件被触发');
});

// 触发 'customEvent' 事件,导致注册的监听器执行
myCustomEmitter.emit('customEvent');

3.1.2 利用自定义事件实现模块之间的通信

通过自定义事件,不同模块之间可以进行松耦合的通信。这样的设计可以提高代码的可维护性和灵活性。

3.2 事件处理模式:

3.2.1 一次性事件监听器

有时候我们只关心事件触发一次,可以使用 once 方法注册一次性监听器:

javascript 复制代码
myEmitter.once('onceEvent', () => {
  console.log('这个监听器只会执行一次');
});

3.2.2 移除事件监听器

通过 removeListener 方法可以移除特定的事件监听器:

javascript 复制代码
const listener = () => {
  console.log('要被移除的监听器');
};

myEmitter.on('removeEvent', listener);

// 移除监听器
myEmitter.removeListener('removeEvent', listener);

3.2.3 监听器的错误处理

事件监听器中的错误可以通过 error 事件进行捕捉,避免整个程序崩溃:

javascript 复制代码
myEmitter.on('error', (err) => {
  console.error('发生错误:', err.message);
});

myEmitter.emit('error', new Error('这是一个错误'));

第四部分:Events 模块的应用场景

4.1 在 HTTP 模块中的应用:

4.1.1 事件模型在 HTTP 服务器中的使用

在 HTTP 模块中,Events 模块的事件模型被广泛应用,允许开发者为不同的 HTTP 事件注册监听器,实现灵活的服务器逻辑。

4.1.2 处理 HTTP 请求和响应的事件

通过 Events 模块,可以监听 HTTP 请求和响应的事件,实现对数据的异步处理,提高服务器的并发性能。

js 复制代码
const http = require('http');
const EventEmitter = require('events');

// 创建事件发射器实例
const eventEmitter = new EventEmitter();

// 创建 HTTP 服务器
const server = http.createServer((req, res) => {
  // 触发 'request' 事件,传递请求和响应对象
  eventEmitter.emit('request', req, res);
});

// 注册 'request' 事件监听器
eventEmitter.on('request', (req, res) => {
  console.log('收到请求');
  
  // 处理请求逻辑
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello, World!');
});

// 启动服务器,监听端口
const PORT = 3000;
server.listen(PORT, () => {
  console.log(`服务器启动,监听端口 ${PORT}`);
});

4.2 在文件系统操作中的应用:

4.2.1 通过 Events 模块实现异步文件读写

在文件系统操作中,通过使用 Events 模块,可以建立更高效的异步文件读写系统,提高文件处理的效率。

js 复制代码
const fs = require('fs');
const EventEmitter = require('events');

class FileHandler extends EventEmitter {
  readFileAsync(filePath) {
    fs.readFile(filePath, 'utf8', (err, data) => {
      if (err) {
        this.emit('error', err);
      } else {
        this.emit('fileRead', data);
      }
    });
  }
}

const fileHandler = new FileHandler();

fileHandler.on('fileRead', (data) => {
  console.log('读取文件成功:', data);
});

fileHandler.on('error', (err) => {
  console.error('发生错误:', err.message);
});

fileHandler.readFileAsync('example.txt');

4.2.2 监听文件变化的应用场景

通过监听文件变化的事件,实现实时文件监控,用于日志文件更新、配置文件变更等场景,提高系统的实时性。

js 复制代码
const fs = require('fs');

fs.watch('example.txt', (eventType, filename) => {
  if (eventType === 'change') {
    console.log(`文件 ${filename} 发生变化`);
  }
});

第五部分 events 模块中常用的函数方法

1. on 方法

描述: 用于注册事件监听器,当特定事件被触发时执行回调函数。

参数:

  • eventName (string): 要监听的事件名称。
  • listener (function): 事件被触发时执行的回调函数。

示例:

javascript 复制代码
const EventEmitter = require('events');
const myEmitter = new EventEmitter();

myEmitter.on('event', () => {
  console.log('事件被触发');
});

myEmitter.emit('event'); // 输出: 事件被触发

2. emit 方法

描述: 触发特定事件,执行所有注册的事件监听器。

参数:

  • eventName (string): 要触发的事件名称。
  • ...args (any): 可选的参数,传递给事件监听器的回调函数。

示例:

javascript 复制代码
const EventEmitter = require('events');
const myEmitter = new EventEmitter();

myEmitter.on('greet', (name) => {
  console.log(`Hello, ${name}!`);
});

myEmitter.emit('greet', 'John'); // 输出: Hello, John!

3. once 方法

描述: 注册一次性事件监听器,仅在特定事件首次触发时执行。

参数:

  • eventName (string): 要监听的事件名称。
  • listener (function): 事件首次触发时执行的回调函数。

示例:

javascript 复制代码
const EventEmitter = require('events');
const myEmitter = new EventEmitter();

myEmitter.once('onceEvent', () => {
  console.log('这个监听器只会执行一次');
});

myEmitter.emit('onceEvent'); // 输出: 这个监听器只会执行一次
myEmitter.emit('onceEvent'); // 无输出,监听器已被移除

4. addListener 方法

描述: on 方法的别名,用于注册事件监听器。

参数:

  • eventName (string): 要监听的事件名称。
  • listener (function): 事件被触发时执行的回调函数。

示例:

javascript 复制代码
const EventEmitter = require('events');
const myEmitter = new EventEmitter();

myEmitter.addListener('event', () => {
  console.log('事件被触发');
});

myEmitter.emit('event'); // 输出: 事件被触发

5. removeListener 方法

描述: 移除特定事件的指定监听器。

参数:

  • eventName (string): 要移除监听器的事件名称。
  • listener (function): 要移除的监听器函数。

示例:

javascript 复制代码
const EventEmitter = require('events');
const myEmitter = new EventEmitter();

const eventHandler = () => {
  console.log('事件处理函数');
};

myEmitter.on('event', eventHandler);
myEmitter.removeListener('event', eventHandler);

myEmitter.emit('event'); // 无输出,监听器已被移除

6. off 方法

描述: removeListener 方法的别名,用于移除特定事件的指定监听器。

参数:

  • eventName (string): 要移除监听器的事件名称。
  • listener (function): 要移除的监听器函数。

示例:

javascript 复制代码
const EventEmitter = require('events');
const myEmitter = new EventEmitter();

const eventHandler = () => {
  console.log('事件处理函数');
};

myEmitter.on('event', eventHandler);
myEmitter.off('event', eventHandler);

myEmitter.emit('event'); // 无输出,监听器已被移除

7. listeners 方法

描述: 返回指定事件的监听器数组。

参数:

  • eventName (string): 要获取监听器的事件名称。

示例:

javascript 复制代码
const EventEmitter = require('events');
const myEmitter = new EventEmitter();

const eventHandler1 = () => {
  console.log('事件处理函数1');
};

const eventHandler2 = () => {
  console.log('事件处理函数2');
};

myEmitter.on('event', eventHandler1);
myEmitter.on('event', eventHandler2);

const listeners = myEmitter.listeners('event');
console.log(listeners); 
// 输出: [ [Function: eventHandler1], [Function: eventHandler2] ]

8. setMaxListeners 方法

描述: 设置单个事件的最大监听器数量,超过该数量时会发出警告。

参数:

  • n (number): 最大监听器数量。

示例:

javascript 复制代码
const EventEmitter = require('events');
const myEmitter = new EventEmitter();

myEmitter.setMaxListeners(2);

myEmitter.on('event', () => {
  console.log('事件处理函数1');
});

myEmitter.on('event', () => {
  console.log('事件处理函数2');
});

myEmitter.on('event', () => {
  console.log('事件处理函数3'); // 警告: Possible EventEmitter memory leak detected.
});

9. eventNames 方法

描述: 返回当前事件发射器上已注册事件的数组。

参数:

示例:

javascript 复制代码
const EventEmitter = require('events');
const myEmitter = new EventEmitter();

myEmitter.on('event1', () => {});
myEmitter.on('event2', () => {});

const eventNames = myEmitter.eventNames();
console.log(eventNames); 
// 输出: [ 'event1', 'event2' ]

10. listenerCount 方法

描述: 返回特定事件的监听器数量。

参数:

  • eventName (string): 要获取监听器数量的事件名称。

示例:

javascript 复制代码
const EventEmitter = require('events');
const myEmitter = new EventEmitter();

myEmitter.on('event', () => {});
myEmitter.on('event', () => {});

const count = myEmitter.listenerCount('event');
console.log(count); // 输出: 2

结语

这篇文章到这里就结束了,感兴趣的话可以订阅收藏哦🎶🎶

相关推荐
Estar.Lee1 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
y先森2 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy2 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189112 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿3 小时前
CSS查缺补漏(补充上一条)
前端·css
2401_857610033 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全
凌冰_4 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
码农飞飞4 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货4 小时前
Rust 的简介
开发语言·后端·rust
吃杠碰小鸡4 小时前
commitlint校验git提交信息
前端