Node.js 事件驱动与EventEmitter深入
事件驱动模式让 Node.js 能高效处理并发 I/O,EventEmitter 是其核心实现。
EventEmitter 内部结构
JavaScript
const EventEmitter = require('events');
// EventEmitter 内部存储
// _events: { eventName: [listener1, listener2, ...] }
// _eventsCount: 监听器总数
// _maxListeners: 最大监听器数量(默认 10)
const emitter = new EventEmitter();
emitter.on('data', () => {});
emitter.on('data', () => {});
console.log(emitter._events);
// { data: [Function, Function] }
监听器执行顺序
JavaScript
// 监听器按添加顺序执行
const emitter = new EventEmitter();
emitter.on('event', () => console.log('A'));
emitter.on('event', () => console.log('B'));
emitter.on('event', () => console.log('C'));
emitter.emit('event');
// 输出: A -> B -> C
// prependListener 添加到头部
emitter.prependListener('event', () => console.log('D'));
// D -> A -> B -> C
emit 执行机制
JavaScript
// emit 同步执行所有监听器
const emitter = new EventEmitter();
emitter.on('sync', () => {
console.log('监听器1');
});
console.log('开始');
emitter.emit('sync');
console.log('结束');
// 输出: 开始 -> 监听器1 -> 结束
// emit 是同步调用,不是异步
异步事件模式
JavaScript
// 异步执行监听器
emitter.on('async', (data) => {
setImmediate(() => {
console.log('异步处理:', data);
});
});
// 或使用 Promise
emitter.on('async', async (data) => {
await processData(data);
});
error 事件处理
JavaScript
// error 事件必须处理
const emitter = new EventEmitter();
// ❌ 未监听 error,进程崩溃
// emitter.emit('error', new Error('错误'));
// ✅ 监听 error 事件
emitter.on('error', (err) => {
console.error('捕获错误:', err.message);
});
emitter.emit('error', new Error('测试错误'));
// 全局 error 处理
EventEmitter.defaultMaxListeners = 20;
// 捕获未处理的 error
process.on('uncaughtException', (err) => {
console.error('未捕获异常:', err);
});
once 一次性监听器
JavaScript
// once 执行一次后自动移除
const emitter = new EventEmitter();
emitter.once('connect', () => {
console.log('首次连接');
});
emitter.emit('connect'); // 输出: 首次连接
emitter.emit('connect'); // 无输出
// once 内部实现
// 包装 listener,执行后调用 removeListener
监听器移除
JavaScript
// 移除监听器需相同引用
function handler() {
console.log('handler');
}
emitter.on('data', handler);
emitter.removeListener('data', handler); // 成功
// ❌ 匿名函数无法移除
emitter.on('data', () => {});
emitter.removeListener('data', () => {}); // 失败,不同引用
// 使用 off 别名(Node.js 10+)
emitter.off('data', handler);
监听器数量限制
JavaScript
// 默认最多 10 个监听器
emitter.on('data', () => {});
// ... 添加超过 10 个会警告
// 设置最大数量
emitter.setMaxListeners(20);
// 获取最大数量
console.log(emitter.getMaxListeners());
// 全局设置
EventEmitter.defaultMaxListeners = 20;
// 无限制(谨慎使用)
emitter.setMaxListeners(0);
自定义事件类
JavaScript
class Database extends EventEmitter {
connect() {
// 模拟连接
setTimeout(() => {
this.emit('connected');
}, 1000);
}
query(sql) {
setTimeout(() => {
this.emit('query', { sql, result: [] });
}, 500);
}
close() {
this.emit('closed');
this.removeAllListeners();
}
}
const db = new Database();
db.on('connected', () => console.log('已连接'));
db.on('query', (data) => console.log('查询:', data.sql));
db.on('closed', () => console.log('已关闭'));
db.connect();
db.query('SELECT * FROM users');
事件命名规范
JavaScript
// 推荐命名
emitter.emit('connection:established');
emitter.emit('user:login');
emitter.emit('data:received');
emitter.emit('error:network');
// 避免冲突
const EVENTS = {
CONNECT: 'connect',
DISCONNECT: 'disconnect',
ERROR: 'error',
DATA: 'data'
};
emitter.on(EVENTS.CONNECT, handler);
emitter.emit(EVENTS.CONNECT);
EventEmitter 性能考量
JavaScript
// 大量监听器影响性能
// 监听器越多,emit 越慢
// 优化:合并相关事件
emitter.on('user', (action, data) => {
switch (action) {
case 'login': handleLogin(data);
case 'logout': handleLogout(data);
}
});
// 避免:大量独立事件
emitter.on('user:login', handler);
emitter.on('user:logout', handler);
// ... 多个事件
捕获所有事件
JavaScript
// 使用 newListener 监听添加
emitter.on('newListener', (event, listener) => {
console.log('添加监听器:', event);
});
// 使用 removeListener 监听移除
emitter.on('removeListener', (event, listener) => {
console.log('移除监听器:', event);
});
// 使用 '*' 捕获所有事件(需自定义)
class WildcardEmitter extends EventEmitter {
emit(event, ...args) {
super.emit('*', event, ...args);
super.emit(event, ...args);
}
}
要点总结
- EventEmitter 内部用 _events 对象存储监听器
- emit 同步执行所有监听器,非异步
- 监听器按添加顺序执行,prependListener 可前置
- error 事件未监听会导致进程崩溃
- 监听器数量默认限制 10,setMaxListeners 调整
- 移除监听器需传入相同函数引用
📝 发现内容有误?点击此处直接编辑