Node.js 简介

events 事件模块与 EventEmitter

掌握 Node.js EventEmitter 的发布订阅用法,理解 Node.js 事件驱动编程的核心思想。

🎯 引言

学完这篇文章,你能理解 Node.js 事件驱动的编程思想,会用 EventEmitter 完成事件的监听、触发和取消,并能用它写出模块之间解耦的代码。


🧱 从浏览器事件说起

你在浏览器里写过按钮点击事件:按钮被点击,绑定的函数就执行。这就是事件驱动:程序不主动轮询,而是“有事发生再处理”。

Node.js 把这套思想搬到了后端,核心工具就是 events 模块提供的 EventEmitter。可以把它想象成订报纸:你先订阅(on),报社一有新报纸就投递给你(emit),你随时也可以退订(off)。


🛠 基本用法:on 与 emit

basic.js
import { EventEmitter } from 'node:events';

const emitter = new EventEmitter();

// 订阅 login 事件
emitter.on('login', (name) => {
    console.log(`欢迎回来,${name}`);
});

// 触发 login 事件,并传递参数
emitter.emit('login', '小明'); // 输出:欢迎回来,小明

三步就完成了一次发布订阅:on 监听事件,emit 触发事件并传参,监听器收到参数后执行。事件名是自定义的字符串,叫什么都可以,约定俗成用名词,比如 loginordererror


🛠 常用方法进阶

methods.js
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 的时候写的是匿名箭头函数,之后是没办法单独移除它的,需要移除的场景请把函数先存成变量。

🪤 这些细节要注意

同一个事件可以挂多个监听器,触发时按订阅顺序依次执行:

multi.js
emitter.on('login', () => console.log('记录日志'));
emitter.on('login', () => console.log('发送通知'));

emitter.emit('login');
// 记录日志
// 发送通知

emit 是同步执行的,触发的那一刻,所有监听器立即按顺序跑完,并不会排队等下一轮事件循环。

不再使用的监听器要记得移除。反复 on 却不 off,监听器会越积越多造成内存泄漏。Node.js 默认同一个事件超过 10 个监听器就会在终端打印警告,这是在提醒你检查代码。

💡 实战:写一个解耦的日志器

发布订阅的价值在于解耦:发消息的人不用关心谁在听、怎么处理。用一个日志器体会一下:

logger.js
import { EventEmitter } from 'node:events';

export const logger = new EventEmitter();

// 日志器自己决定怎么处理 log 事件
logger.on('log', (message) => {
    const time = new Date().toLocaleTimeString();
    console.log(`[${time}] ${message}`);
});
app.js
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

order.js
import { EventEmitter } from 'node:events';

const emitter = new EventEmitter();

// 请在这里编写代码

🎉 恭喜你已经完成 Node.js 入门课程!现在你已经掌握 Node.js 的核心概念,以及 fs、path、http、events 等常用内置模块。接下来可以学习 Express 框架,用更高效的方式搭建完整的后端服务。