events 事件模块与 EventEmitter
🎯 引言
学完这篇文章,你能理解 Node.js 事件驱动的编程思想,会用 EventEmitter 完成事件的监听、触发和取消,并能用它写出模块之间解耦的代码。
🧱 从浏览器事件说起
你在浏览器里写过按钮点击事件:按钮被点击,绑定的函数就执行。这就是事件驱动:程序不主动轮询,而是“有事发生再处理”。
Node.js 把这套思想搬到了后端,核心工具就是 events 模块提供的 EventEmitter。可以把它想象成订报纸:你先订阅(on),报社一有新报纸就投递给你(emit),你随时也可以退订(off)。
🛠 基本用法:on 与 emit
import { EventEmitter } from 'node:events';
const emitter = new EventEmitter();
// 订阅 login 事件
emitter.on('login', (name) => {
console.log(`欢迎回来,${name}`);
});
// 触发 login 事件,并传递参数
emitter.emit('login', '小明'); // 输出:欢迎回来,小明
三步就完成了一次发布订阅:on 监听事件,emit 触发事件并传参,监听器收到参数后执行。事件名是自定义的字符串,叫什么都可以,约定俗成用名词,比如 login、order、error。
🛠 常用方法进阶
import { EventEmitter } from 'node:events';
const emitter = new EventEmitter();
// once:只响应一次,触发后自动移除
emitter.once('start', () => {
console.log('程序启动了');
});
emitter.emit('start'); // 输出:程序启动了
emitter.emit('start'); // 没有输出
// off:手动移除指定的监听器
function onLogin(name) {
console.log(`${name} 登录了`);
}
emitter.on('login', onLogin);
emitter.off('login', onLogin); // 退订
emitter.emit('login', '小明'); // 没有输出
// eventNames:查看当前订阅了哪些事件
console.log(emitter.eventNames()); // []
off 移除监听器时,传入的必须是同一个函数引用。如果 on 的时候写的是匿名箭头函数,之后是没办法单独移除它的,需要移除的场景请把函数先存成变量。🪤 这些细节要注意
同一个事件可以挂多个监听器,触发时按订阅顺序依次执行:
emitter.on('login', () => console.log('记录日志'));
emitter.on('login', () => console.log('发送通知'));
emitter.emit('login');
// 记录日志
// 发送通知
emit 是同步执行的,触发的那一刻,所有监听器立即按顺序跑完,并不会排队等下一轮事件循环。
on 却不 off,监听器会越积越多造成内存泄漏。Node.js 默认同一个事件超过 10 个监听器就会在终端打印警告,这是在提醒你检查代码。💡 实战:写一个解耦的日志器
发布订阅的价值在于解耦:发消息的人不用关心谁在听、怎么处理。用一个日志器体会一下:
import { EventEmitter } from 'node:events';
export const logger = new EventEmitter();
// 日志器自己决定怎么处理 log 事件
logger.on('log', (message) => {
const time = new Date().toLocaleTimeString();
console.log(`[${time}] ${message}`);
});
import { logger } from './logger.js';
// 业务代码只管发事件,不关心日志怎么打印
logger.emit('log', '用户小明登录了');
logger.emit('log', '用户小明退出了');
以后想把日志改成写入文件,只需要改 logger.js 一处,所有业务代码都不用动。Node.js 的很多核心模块(http 的 server、文件流等)都建立在 EventEmitter 之上,理解了它,后面学 Express 会轻松很多。
🧾 小节总结
- EventEmitter 是 Node.js 事件驱动的核心:订阅事件,有事发生再处理。
- 基本三件套:
on监听、emit触发并传参、off移除。 once只响应一次,eventNames查看当前订阅的事件。- 同一事件的多个监听器按顺序同步执行,
emit本身不是异步的。 - 发布订阅让模块之间解耦,日志、通知等横向功能非常适合用它实现。
❓ 知识问答
Q1:on 和 addListener 有区别吗?
没有,两者是同一个方法的两个名字,用哪个都可以,社区里 on 更常见。
Q2:emit 是同步还是异步?
同步。emit 调用时,所有监听器会立即按顺序执行完,然后才继续执行 emit 后面的代码。
Q3:once 触发之后还需要手动 off 吗?
不需要。once 注册的监听器在触发后会自动移除。
Q4:EventEmitter 和浏览器的事件是一回事吗?
思想相同,实现是两套。浏览器事件由 DOM 提供,EventEmitter 是 Node.js 自己的实现,用法相近但 API 不完全一样,不要混用概念。
🧪 小练习
模拟一个简单的下单流程:创建 order.js,订阅 order 事件并打印“收到订单:xxx”,订阅 pay 事件并只响应一次,打印“支付成功”。然后依次触发一次 order(参数:奶茶)、两次 pay。
import { EventEmitter } from 'node:events';
const emitter = new EventEmitter();
// 请在这里编写代码
🎉 恭喜你已经完成 Node.js 入门课程!现在你已经掌握 Node.js 的核心概念,以及 fs、path、http、events 等常用内置模块。接下来可以学习 Express 框架,用更高效的方式搭建完整的后端服务。
