深入探讨:Node.js中的EventEmitter与浏览器中的EventTarget
在Node.js
中,EventEmitter
是一个非常重要的模块,它是Node.js
实现事件驱动的基础。
在浏览器中,EventTarget
是一个非常重要的接口,它是浏览器实现事件驱动的基础。
本文将深入探讨Node.js
中的EventEmitter
与浏览器中的EventTarget
的实现细节,以及它们之间的异同。
EventEmitter
在Node.js
的events
模块中,EventEmitter
是一个非常重要的类,很多模块都继承了它,比如Stream
、HTTP
、Net
等。
EventEmitter
的使用非常简单,它提供了on
、once
、emit
、removeListener
等方法,用于注册事件监听器、触发事件、移除事件监听器等。
const EventEmitter = require('events').EventEmitter;
// 创建一个EventEmitter实例
const emitter = new EventEmitter();
// 注册一个事件监听器
emitter.on('event', () => {
console.log('event emitted');
});
// 触发事件
emitter.emit('event');
非常熟悉的使用方式,这种方式在我们使用js
的一些操作非常常见,例如:
const button = document.querySelector('button');
button.addEventListener('click', () => {
console.log('button clicked');
});
// 使用 jQuery 就更加熟悉了
$('button').on('click', () => {
console.log('button clicked');
});
而使用addEventListener
注册事件监听器在EventEmitter
也是有的,不过它叫做addListener
,on
和addListener
是等价的。
const EventEmitter = require('events').EventEmitter;
const emitter = new EventEmitter();
// 注册一个事件监听器
emitter.addListener('event', () => {
console.log('event emitted');
});
// 触发事件
emitter.emit('event');
只不过addListener
已经被标记为deprecated
了,所以我们一般使用on
。
EventEmitter 的属性和方法
上面已经提到了EventEmitter
提供了on
、once
、emit
、removeListener
等方法,除此之外,它还提供了一些其他的方法。
这些方法我们简单的认识一下常用的就可以了,因为它们的作用都是为了让我们更方便的使用EventEmitter
。
addListener
:注册事件监听器,等价于on
。on
:注册事件监听器,接受两个参数,第一个参数是事件名称,第二个参数是事件监听器。once
:注册事件监听器,只会触发一次,触发后会自动移除。removeListener
:移除事件监听器,接受两个参数,第一个参数是事件名称,第二个参数是事件监听器。removeAllListeners
:移除所有事件监听器,如果指定了事件名称,则只会移除指定事件的所有事件监听器。off
:移除事件监听器,等价于removeListener
。
其他的方法我们可以在官方文档中查看。
EventEmitter 的一些用法
EventEmitter
本身在nodejs
中就被广泛的使用,并且nodejs
把这个功能放开了,我们则可以使用它来实现一些自己的功能,现在我们就来看一些例子。
我们可以使用EventEmitter
来实现一个简单的事件总线,这个事件总线可以用来在不同的模块之间传递事件。
// event-bus.js
const EventEmitter = require('events').EventEmitter;
const eventBus = new EventEmitter();
module.exports = eventBus;
// module-a.js
const eventBus = require('./event-bus');
eventBus.on('event', () => {
console.log('event emitted');
});
// module-b.js
const eventBus = require('./event-bus');
eventBus.emit('event');
当然这是最基础的用法,正常情况下都是这样使用的,代码很简单,但是却可以实现很多功能,例如在nodejs
中,Stream
模块就是使用EventEmitter
来实现的。
上面截图就是Stream
模块的源码,我们可以看到Stream
模块继承了EventEmitter
,并且在Stream
模块中使用了EventEmitter
的一些方法。
EventTarget
在nodejs
中也有一个EventTarget
,他的API
和浏览器中的EventTarget
是一样的;
但是在nodejs
中的EventTarget
和浏览器中的EventTarget
是不一样的,它们的实现方式也不一样。
最大的差别是nodejs
中的事件不会冒泡,而浏览器中的事件会冒泡,这里要说的就是浏览器中的EventTarget
。
EventTarget 的属性和方法
EventTarget
提供了addEventListener
、removeEventListener
、dispatchEvent
等方法,我们先来看一下这些方法的作用。
addEventListener
:注册事件监听器,接受三个参数,第一个参数是事件名称,第二个参数是事件监听器,第三个参数是一个对象,用于指定事件监听器的一些配置。removeEventListener
:移除事件监听器,接受三个参数,第一个参数是事件名称,第二个参数是事件监听器,第三个参数是一个对象,用于指定事件监听器的一些配置。dispatchEvent
:触发事件,接受一个参数,这个参数是一个事件对象。
就这三个方法,没有其他的了,详情可以查看官方文档。
EventTarget 的一些用法
EventTarget
在浏览器中的使用非常广泛,其中的地位就像EventEmitter
在nodejs
中的地位一样,我们可以使用EventTarget
来实现一些功能。
但是通常我们使用也和nodejs
中的EventEmitter
一样,道理是这个道理,但是使用场景还是不同。
通常也是继承EventTarget
来实现一些功能,上面给的是nodejs
中最简单的例子,我们来看一个稍微高级点的用法:
例如我们可以保证同一时间只有一个请求发送到服务器,这样就可以避免发送重复的请求。
const eventTarget = new EventTarget();
let isSending = false;
const fetchApi = (callback) => {
const _callback = (event) => {
callback(event);
eventTarget.removeEventListener('send', _callback);
};
eventTarget.addEventListener('send', _callback);
if (isSending) {
return;
}
isSending = true;
const event = new Event('send');
fetch('/api').then((res) => {
// do something
event.data = res;
}).catch((err) => {
event.error = err;
}).finally(() => {
isSending = false;
eventTarget.dispatchEvent(event);
});
}
fetchApi((res) => {
console.log(res);
});
fetchApi((res) => {
console.log(res);
});
fetchApi((res) => {
console.log(res);
});
可以看到有三次输出,但是报错只有一个404
,这说明只有一次请求发送到了服务器。
区别
EventEmitter
和EventTarget
都是用来实现事件的,但是它们之间还是有一些区别的。
首先就是上面说过的例子,EventTarget
会有事件冒泡,而EventEmitter
没有事件冒泡。
因为在nodejs
中是不存在层级关系的,所有的模块都是平级的,所以EventEmitter
也就没有事件冒泡。
而在浏览器中,DOM
元素天然就有层级关系,事件冒泡也就是自然而然的事情了,如果没有事件冒泡,那么事件的传递就会变得非常麻烦。
其次就是EventEmitter
和EventTarget
的实现方式不同;
EventEmitter
是一个发布订阅模式的实现,而EventTarget
是一个观察者模式的实现。
这两种模式的区别在于,发布订阅模式中,发布者和订阅者是没有关系的,发布者只负责发布事件,订阅者只负责订阅事件,发布者和订阅者之间没有任何关系。
而观察者模式中,观察者和被观察者是有关系的,观察者会观察被观察者的变化,当被观察者发生变化时,观察者会收到通知。
这也是为什么EventEmitter
中事件触发是可以直接通过字符串,而EventTarget
中事件触发需要通过事件对象的原因。
同时也是因为这个原因他们的API
也有所不同,EventEmitter
中会有once
这种只执行一次就移除的方法,而EventTarget
中没有这种方法。
在EventTarget
必须得自己手动处理只执行一次的逻辑,这也是为什么上面的例子中会有这样的代码:
eventTarget.addEventListener('send', (event) => {
callback(event);
eventTarget.removeEventListener('send', callback);
});
其实用EventEmitter
的话,就不需要这样的代码了,因为EventEmitter
中有once
方法,可以直接这样写:
eventEmitter.once('send', callback);
在nodejs
的events
模块中,其实也有EventTarget
的实现,但是它并不是继承EventEmitter
的,而是改写了EventEmitter
的实现,使其符合EventTarget
的规范。
他们的区别同样也是没有事件冒泡,同时运行环境不同,服务的对象也不同。
总结
EventEmitter
和EventTarget
都是用来实现事件的,但是它们之间还是有一些区别的。
最大的区别就是一个是node
环境,一个是浏览器环境,为了服务不同的环境,它们的实现方式也不同。
EventEmitter
是一个发布订阅模式的实现,而EventTarget
是一个观察者模式的实现。
EventEmitter
中事件触发是可以直接通过字符串,而EventTarget
中事件触发需要通过事件对象。
转载自:https://juejin.cn/post/7249286832160358457