有没有觉得Node.js的EventEmitter设计的有点反人类?
你有一个eventemitter e, 然后你要发送事件的时候用e.emit(...), 然后这个emit其实是发给e自己的,你在接受的时候要用e.on(...). 如果你的发送事件和接受事件在不同的文件里,你还要想办法共享这个e.
我想象中的API应该是,发送端 src.emit('eventType', dest, ...args), 接收端dest.on('eventType', ...args), 当然这里也涉及到共享dest的问题
是不是这样呢?还是我的理解有误?谢谢了
链接:https://www.zhihu.com/question/419484961/answer/1942765394260633362
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
一、EventEmitter模块简介
事件发射器EventEmitter是Node.js中实现事件驱动编程的核心模块,经常被用于实现发布/订阅的通信模式。
EventEmitter内部维护了一个event键值对,键为事件名,值为回调函数数组,事件触发时按注册的顺序同步执行回调函数。
EventEmitter类的设计允许一个对象触发事件,然后让其他对象监听并响应这些事件。
事件驱动编程除了使用Node.Js语法支持的EventEmitter,也可以使用原生浏览器支持的JavaScript接口EventTarget。
EventEmitter在编码时的使用步骤:
1.创建事件发射器实例。
2.注册事件监听器。
a.监听用户自定义事件。
b.监听错误事件,未被监听的error事件会导致Node.js进程崩溃。
3.应用程序中触发事件。
4.应用程序根据需要移除监听器。
创建EventEmitter实例的基础语法:
//引入events模块
var events = require('events');
//创建eventEmitter对象
var eventEmitter = new events.EventEmitter();
二、EventEmitter模块的常用接口函数
1.addListener(event, listener)
为事件添加监听器。
2.on(event, listener)
注册事件监听器。
3.emit(event, [arg1], [arg2], [...])
触发事件监听器,并传递参数。
4.removeListener(event, listener)
移除指定事件的某个监听器。
5.removeAllListeners([event])
移除指定事件的所有监听器。
代码样例:
const EventEmitter = require('events');
// 1. 创建事件发射器实例
class MyEmitter extends EventEmitter {}
const emitter = new MyEmitter();
// 2. 注册事件监听器
emitter.on('downloadComplete', (fileName) => {
console.log(`${fileName} download complete!`);
});
// 3. 触发事件(可传参数)
emitter.emit('downloadComplete', 'music.mp3');
运行结果:
music.mp3 download complete!
三、EventEmitter特殊语法
1.为同一个Event事件创建多个监听器,多个监听器将按照添加顺序被依次触发。
const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
eventEmitter.on('data', (data) => {
console.log('Listener 1 received:', data);
});
eventEmitter.on('data', (data) => {
console.log('Listener 2 received:', data);
});
eventEmitter.on('data', (data) => {
console.log('Listener 3 received:', data);
});
eventEmitter.emit('data', 'Sample data');
运行结果:
Listener 1 received: Sample data
Listener 2 received: Sample data
Listener 3 received: Sample data
2.利用"once"接口,让监听器只能被触发一次。
Demo1:支持连续触发
const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
eventEmitter.on('connect', () => {
console.log('Connected!');
});
eventEmitter.emit('connect');
eventEmitter.emit('connect');
运行结果:
Connected!
Connected!
Demo2:只触发一次
const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
eventEmitter.once('connect', () => {
console.log('Connected!');
});
eventEmitter.emit('connect');
eventEmitter.emit('connect');
运行结果:
Connected!
3.调用removeListener函数移除监听器,释放资源。
const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
const listener = (data) => {
console.log('test Event Triggered: ', data);
};
eventEmitter.on('test', listener);
eventEmitter.emit('test', '1');
eventEmitter.emit('test', '2');
eventEmitter.removeListener('test', listener);
eventEmitter.emit('test', '3');
运行结果:
test Event Triggered: 1
test Event Triggered: 2
四、手动实现EventEmitter类
设计思路:
1、EventEmitter构造函数:维护一个listeners键值对。
2、on()为事件注册监听器:给键值对赋值,key为Event事件名称,value为对应的回调函数。
3、emit()触发监听器:调用key对应的回调函数,调用的时候传入参数data。
4、addEventListener()为事件添加监听器:实现方式同on()函数。
5、removeEventListener()为事件移除监听器:从listeners中删除键值对。
完整Node.Js代码如下:
class SimpleEmitter {
constructor() {
this.listeners = {}
}
on(str, fn) {
this.listeners[str] = fn
}
emit(str, data) {
this.listeners[str](data)
}
addEventListener(str,fn) {
this.on(str,fn)
}
removeEventListener(str) {
delete this.listeners[str]
}
}
const emitter = new SimpleEmitter()
emitter.on("test", (data)=>{
console.log(`Test triggered :`, data)
}
)
emitter.emit("test", "1")
emitter.emit("test", "2")
emitter.emit("test", "3")
运行结果:
Test triggered : 1
Test triggered : 2
Test triggered : 3
五、代码实战
Demo1:基于EventEmitter实现的日志系统:
const EventEmitter = require('events');
class Logger extends EventEmitter {
log(message) {
this.emit('message', `${new Date().toISOString()} - ${message}`);
}
}
const logger = new Logger();
logger.on('message', (msg) => {
console.log(msg);
});
logger.log('sysmtem started');
运行结果:
2025-05-02T12:40:53.615Z - sysmtem started
Demo2:EventEmitter与EventTarget用法对比
EventTarget实现事件驱动编程的核心接口函数:
1.addEventListener(type, listener, options):注册事件监听
2.removeEventListener(type, listener, options):移除事件
3.dispatchEvent(event):触发事件监听
//Node.js EventEmitter
const EventEmitter = require('events');
class Sensor extends EventEmitter {}
const sensor = new Sensor();
sensor.on('data', (value, timestamp) => {
console.log(`get timestamp: ${value} @ ${timestamp}`);
});
sensor.emit('data', 23.5, Date.now());
//浏览器 EventTarget
const target = new EventTarget();
target.addEventListener('data', (event) => {
const { value, timestamp } = event.detail;
console.log(`get timestamp: ${value} @ ${timestamp}`);
});
target.dispatchEvent(new CustomEvent('data', {
detail: { value: 23.5, timestamp: Date.now() }
}));
运行结果:
get timestamp: 23.5 @ 1746192066422
get timestamp: 23.5 @ 1746192066432
Demo3:基于EventTarget实现的电影订票系统
<!DOCTYPE html>
<html>
<head>
<title>纯前端电影订票系统</title>
<style>
.seat {
width: 50px; height: 50px;
margin: 5px; display: inline-block;
border: 2px solid #666;
text-align: center; line-height: 50px;
cursor: pointer;
transition: all 0.3s;
}
.available { background: #cfc; }
.booked { background: #fcc; }
</style>
</head>
<body>
<h2>选择座位:</h2>
<div id="seatMap"></div>
<script>
// 自定义事件总线(模拟EventEmitter)
class EventBus extends EventTarget {
emit(eventName, detail) {
this.dispatchEvent(new CustomEvent(eventName, { detail }));
}
}
// 创建全局事件总线
const bus = new EventBus();
// 座位状态管理
const seatStore = {
seats: {
'A1': 'available',
'A2': 'available',
'B1': 'available'
},
bookSeat(seat) {
if (this.seats[seat] === 'available') {
this.seats[seat] = 'booked';
bus.emit('seat-updated', { seat, status: 'booked' });
return true;
}
return false;
}
};
// 界面渲染组件
class SeatMap {
constructor(containerId) {
this.container = document.