手写工具- EventTarget/EventEmitter
1. 事件溯源-理解下JS 如何执行
追根溯源,我们先了解下为啥我们在 JS 里经常看到 Event 处理(promise、callback、鼠标事件等)?实际这个源自 JS 底层执行逻辑是事件驱动的,所以我们稍微了解下 JS 的单线程事件驱动模型
1.1 单线程事件驱动之单线程执行
Js 是单线程即只能一个事情一个事情去干,这个源自于 js 设计之初就是用于网页简单交互,不需要复杂的运算,如果引入多线程模型,则需要考虑线程同步问题。
JS 单线程的优势:写代码简单,不需要考虑多线程
JS 单线程的劣势:不适合执行 CPU 密集型任务或者长时间运行的计算,这些任务会导致后续任务被阻塞,即表现程序执行缓慢
1.2 单线程事件驱动之事件驱动模型
JS 是单线程但是为啥这么快,IO 并发很高呢?这是因为 JS 采用事件驱动模型(Event Loop),简单讲即一个线程一个接一个处理任务队列里待执行的任务(比如鼠标点击一下),如果一个任务很耗时间(IO 请求等任务),将这个耗时任务丢给辅线程去处理,主线程继续执行后续的任务,等辅线程处理完了在任务队列塞入一个处理回调任务(处理辅线程任务直接结果),这样耗时的任务不会卡住主线程。
相关名词:Event Loop、系统内核 I/O 多路复用
2. 手写EventTarget、EventEmitter
通过上面应该可以看到event概念贯穿在js执行逻辑里,这里我们手写一下核心类
2.1 手写EventTarget
WebApi:https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/EventTarget
DOM 的事件操作(监听和触发),都定义在 EventTarget 接口,该接口提供的方法有:
EventTarget.addEventListener()
:对事件进行监听EventTarget.removeEventListener()
:移除事件监听EventTarget.dispatchEvent()
: 触发事件
//伪代码
//处理事件
let onClick=(ev:IEvent)=>console.log("点击!!");
//监听事件
button.addEventListener('click', onClick, false);
//移除监听事件
button.removeEventListener('click', onClick);
//主动触发事件
button.dispatchEvent(ClickEvent{})
这里我们开始构建一个通用的 EventTarge 用于自己的日常项目中,上面 WebApi 里的 EventTarget 是用于浏览器事件用的,我们做些简化用以更适用日常代码:
- EventTarget 管理了多个事件,我们调整为针对单个事件,多个事件管理留给 EventEmitter
- EventTarget 的传递事件对象是 DomEvent,我们调整为任意对象
export class EventTarget<T = void> {
protected listener: ((event: T) => void | boolean)[] = [];
/**
* 是否激活事件管理器,默认:true
*
* 在激活状态下触发事件才会触发监听器
*/
beActive = true;
/**
* 添加监听器;监听函数返回true则结束监听
* @param func
*/
addEventListener(func: (event: T) => void | boolean) {
this.listener.push(func);
}
/**
* 移除监听器
* @param func
*/
removeEventListener(func: (event: T) => void | boolean) {
const index = this.listener.indexOf(func);
if (index >= 0) {
this.listener.splice(index,1);
}
}
/**
* 移除所有监听器
*/
removeAllListeners() { this.listener = []; }
/**
* 触发事件
* @param event
*/
raiseEvent(event: T) {
if (this.beActive) {
for (let i = 0; i < this.listener.length;) {
let func = this.listener[i];
let result = func(event);
if (result) {
this.listener.splice(i, 1);
} else {
i++;
}
}
}
}
}
在上面代码中加了一些额外的概念
- 是否激活,可以在一些时候设置不触发事件
- 自动移除监听,处理事件函数返回 true 就会自动移除监听,应对场景不用主动掉 removeEventListener
2.2 手写EventEmitter
NodejsApi:https://nodejs.org/docs/latest/api/events.html
Node.js 的 EventEmitter 是一个用于处理事件的核心模块。提供了方法有:
EventEmitter.on(eventName, listener)
:对事件进行监听EventEmitter.off(eventName, listener)
:移除事件监听emitter.emit(eventName[, ...args])
: 触发事件- 其他接口略…
const EventEmitter = require('node:events');
const myEmitter = new EventEmitter();
// First listener
myEmitter.on('event', function firstListener() {
console.log('Helloooo! first listener');
});
// Second listener
myEmitter.on('event', function secondListener(arg1, arg2) {
console.log(`event with parameters ${arg1}, ${arg2} in second listener`);
});
// Third listener
myEmitter.on('event', function thirdListener(...args) {
const parameters = args.join(', ');
console.log(`event with parameters ${parameters} in third listener`);
});
console.log(myEmitter.listeners('event'));
myEmitter.emit('event', 1, 2, 3, 4, 5);
// Prints:
// [
// [Function: firstListener],
// [Function: secondListener],
// [Function: thirdListener]
// ]
// Helloooo! first listener
// event with parameters 1, 2 in second listener
// event with parameters 1, 2, 3, 4, 5 in third listener
这里我们开始构建一个通用的 EventEmitter 用于自己的日常项目中,我们进行主题实现,实现上和上面差不多,比较麻烦在 Typescript 里达成调用接口时能有相应的事件类型提示。
export class EventEmitter<T = { [key: string]: any }> {
protected _listener = {} as any;
/**
* 激活/禁用 事件管理器,在激活状态下,触发事件才会触发监听器
*/
beActive = true;
/**
* 为指定事件添加一个监听器到监听器数组的尾部
* @param ev 事件str
* @param callback 监听函数
*/
on<K extends keyof T>(ev: K, callback: (ev: T[K]) => void | boolean) {
if (this._listener[ev] == null) this._listener[ev] = [];
this._listener[ev].push(callback);
}
/**
* 触发指定事件,并传递参数
* @param ev 事件str
* @param params 事件传递的可选参数
*/
emit<K extends keyof T>(ev: K, params?: T[K]) {
if (this.beActive) {
let listeners = this._listener[ev];
if (listeners) {
for (let i = 0; i < listeners.length;) {
let func = listeners[i];
let result = func(params);
if (result) {
listeners.splice(i, 1);
} else {
i++;
}
}
}
}
}
/**
* 取消对指定事件的监听
* @param ev 事件str
* @param callback 监听函数
*/
off<K extends keyof T>(ev: K, callback: (ev: T[K]) => void | boolean) {
if (this._listener[ev]) {
const index = this._listener[ev].indexOf(callback);
if (index >= 0) {
this._listener[ev].splice(index, 1);
}
}
}
/**
* 移除指定事件的所有监听器
* @param ev 事件
*/
removeEventListeners<K extends keyof T>(ev: K) {
this._listener[ev] = [];
}
/**
* 移除所有事件的监听器
*/
removeAllListeners() {
this._listener = {};
}
}