有没有觉得Node.js的EventEmitter设计的有点反人类?

 

 
 
 
 

你有一个eventemitter e, 然后你要发送事件的时候用e.emit(...), 然后这个emit其实是发给e自己的,你在接受的时候要用e.on(...). 如果你的发送事件和接受事件在不同的文件里,你还要想办法共享这个e.

我想象中的API应该是,发送端 src.emit('eventType', dest, ...args), 接收端dest.on('eventType', ...args), 当然这里也涉及到共享dest的问题

是不是这样呢?还是我的理解有误?谢谢了

 

 

作者:开发者CoderZZ
链接: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.getElementById(containerId);
                bus.addEventListener('seat-updated', this.render.bind(this));
                this.init();
            }

            init() {
                this.render();
                this.container.addEventListener('click', (e) => {
                    if (e.target.classList.contains('seat')) {
                        const seat = e.target.textContent;
                        bus.emit('booking-request', seat);
                    }
                });
            }

            render() {
                this.container.innerHTML = Object.entries(seatStore.seats)
                    .map(([seat, status]) =>
                        `<div class="seat ${status}">${seat}</div>`
                    ).join('');
            }
        }

        // 事件处理
        bus.addEventListener('booking-request', (e) => {
            const seat = e.detail;
            if (seatStore.bookSeat(seat)) {
                console.log(`成功预订座位: ${seat}`);
                alert('预订成功!');
            } else {
                console.warn(`座位 ${seat} 不可用`);
                alert('座位已被预订!');
            }
        });

        // 初始化系统
        new SeatMap('seatMap');
    </script>
</body>
</html>

posted on 2025-09-03 11:17  漫思  阅读(5)  评论(0)    收藏  举报

导航