您现在的位置是:亿华云 > IT科技
EventEmitter 的核心功能实现
亿华云2025-10-04 00:53:39【IT科技】8人已围观
简介大家好,我是前端西瓜哥。EventEmitter 是频率较高的前端面试题。EventEmitter 是 Nodejs 环境下才能使用的库,所以不能直接用于浏览器环境的开发。所以我
大家好,心功现我是心功现前端西瓜哥。
EventEmitter 是心功现频率较高的前端面试题。
EventEmitter 是心功现 Nodejs 环境下才能使用的库,所以不能直接用于浏览器环境的心功现开发。所以我考虑自己实现一套逻辑,心功现自己定制的心功现话也容易根据实际情况的变动做修改。
因此我决定了解一下 EventEmitter 的心功现 API,并尝试自己实现一套逻辑。心功现
Nodejs 的心功现 EventEmitter API首先当然是要了解需求,即 EventEmitter 的心功现 API 使用。详细使用方式请查阅 官方文档,心功现我这里只简单叙述一些常用的心功现 API。
const { EventEmitter,心功现 errorMonitor } = require(events);
// 创建事件触发器实例
const emitter = new EventEmitter()
// on:注册监听者函数。可以注册多个监听函数,心功现
// 触发事件后,会依次同步执行,顺序为绑定时的顺序。
// 别名为:addListener
emitter.on(event, function(a, b) => {
console.log(event emit!, a, b)
})
// once:注册一个只会被执行一次的函数
emitter.once(event, function() => {
console.log(event emit only once!)
})
// emit:触发事件,服务器托管可提供参数。
// 如果有对应监听器函数,会返回 true,否则返回 false
emitter.emit(event, 3, 4)
// 比较特别的是,如果没有注册 error 事件的监听者,
// 触发 error 时,错误不会被捕获而直接报错;
// 若注册,则错误会被捕获
emitter.emit(error, new Error(whoops!))
// event.errorMonitor 是一个 Symbol
// 能够在触发 error 事件时,先执行被绑定的监视器函数
emitter.on(errorMonitor, err => {
console.log(error monitor)
});
// 移除指定监听器,别名为:removeListener
emitter.off(eventName, handler)
// 获取注册的事件名的数组形式
emitter.eventNames()监听者函数的 this 会指向 EventEmitter 实例。当然你可以使用各种方法修改 this 的指向,如箭头函数或 bind 方法。每次添加监听器时,都会触发 newListener 事件,传入的参数为事件名(eventName)和监听器函数(listener)。同样,移除监听器时,会触发 removeListener 事件。emitter.prependListener():同 on,但会添加到监听器数组的开头。...API 很多,但我不打算实现了这么多,香港云服务器就只实现最常用的 on、emit、off。
实现首先,我们知道不同的事件是有特定的 eventName(事件名)的,通过指定 eventName,我们才能绑定对应的多个监听器(函数),才能触发事件执行绑定的这些监听器。
这时候,我们就涉及到数据结构与算法的存储问题了。因为结构和算法是相辅相成的,选择不同的数据结构,使用的算法就会不同。
不同的数据结构与算法的优点的缺陷各不相同,比如空间复杂度上或时间复杂度上的效率不同。
listener 函数的存储那么如何存储呢?常见的云南idc服务商方法是使用哈希表,因为时间复杂度是 O(1),空间复杂度一般也不会太大。JavaScript 的对象本质上就是哈希表。所以我们的存储方式是:
this.hashMap = {
event1: [listener1, listenr2],
event2: [],
}一些可扩展的点:
哈希表的一个问题是:无序。可以通过额外使用一个数组来记录添加 eventName 的记录顺序。这样的话,实现 emitter.eventNames() 可以拿到有序的事件数据。当然这样的需求比较少见,这里只是简单提一下。如果要实现 once(设置执行一次就不再执行的监听器函数),则需要对函数标记,这时候可以考虑让数组元素的格式改为 { listener: Listener, once: boolean },在触发事件的时候,执行监听器函数时,将 once 值为 true 的监听器从数组中移除。可以改为链表实现存储,这样移除中间监听器时,时间复杂度可以变成 O(1)。另外数组删除元素的时间复杂度是 O(n)。但会引入实现上的复杂度,因为没有内置的链表实现,需要自己手动实现一个没有 BUG 的链表类。on() 的实现on() 的实现,其实就是将监听器函数绑定到指定事件对应的数组中。实现起来并不难,只要注意如果是第一次添加指定事件时,要先初始化一个空数组即可。on 最后返回了 this,是为了实现链式调用。
class EventEmiter {
on(eventName, listener) {
if (!this.hashMap[eventName]) {
this.hashMap[eventName] = []
}
this.hashMap[eventName].push(listener)
return this
}
}off() 的实现off() 会根据传入的事件名,找到对应的监听器数组,从中移除指定监听器。同样为了实现链式调用返回了 this。
class EventEmiter {
off(eventName, listener) {
const listeners = this.hashMap[eventName]
if (listeners && listeners.length > 0) {
const index = listeners.indexOf(listener)
if (index > -1) {
listeners.splice(index, 1)
}
}
return this
}
}emit() 的实现emit() 的实现很简单,找到事件对应的监听器,传入参数依次执行。如果事件没有绑定监听器,返回 false。否则,返回 true。
class EventEmiter {
emit(eventName, ...args) {
const listeners = this.hashMap[eventName]
if (!listeners || listeners.length === 0) return false
listeners.forEach(listener => {
listener(...args)
})
return true
}
}完整实现虽然很突然,我这里给出的是 TypeScript 实现,只要将类型声明去掉就是 JavaScript 实现了。当然下面代码是做了简单的单元测试的,大概是没问题的。
源码地址:
type EventName = string | symbol
type Listener = (...args: any[]) => void
class EventEmiter {
private hashMap: { [eventName: string]: Array
on(eventName: EventName, listener: Listener): this {
const name = eventName as string
if (!this.hashMap[name]) {
this.hashMap[name] = []
}
this.hashMap[name].push(listener)
return this
}
emit(eventName: EventName, ...args: any[]): boolean {
const listeners = this.hashMap[eventName as string]
if (!listeners || listeners.length === 0) return false
listeners.forEach(listener => {
listener(...args)
})
return true
}
off(eventName: EventName, listener: Listener): this {
const listeners = this.hashMap[eventName as string]
if (listeners && listeners.length > 0) {
const index = listeners.indexOf(listener)
if (index > -1) {
listeners.splice(index, 1)
}
}
return this
}
}因为对象不支持 Symbol 作为索引,所以这里的实现做了类型的强转。未来,TypeScript 可能会允许对象索引为 Symbol,Enum 等,但目前不行。
很赞哦!(77)
相关文章
- 为什么起域名意义非凡?起域名有什么名堂?
- Serverless 在大规模数据处理的实践
- 图解代理,3分钟学会使用Nginx实现反向代理
- Nginx搭建前端静态服务器+文件服务器
- 其次,一般域名注册有一个获取密码的按钮,域名注册商点击后会向您发送密码。在得到域名注册商发送的密码后,将其传输到域名服务提供商网站,然后输入密码,此时域名呈现申请状态。提交申请后,原注册人通常会向您发送一封电子邮件,询问您是否同意转让。此时,您只需点击同意转移按钮,域名注册商就可以成功转移。
- 快速发展的现代化数据中心催生的 9 大热门职业
- 你懂怎么建立 FTP 服务器么?
- 既然启动流程不太了解,那你知道Tomcat的生命周期是什么样子的么?
- 公司名字不但要与其经营理念、活动识别相统一,还要能反映公司理念,服务宗旨、商品形象,从而才能使人看到或听到公司的名称就能产生愉快的联想,对商店产生好感。这样有助于公司树立良好的形象。
- 科技推动时代发展,浅谈IT技术如何改善数据中心运维管理