手写工具- EventTarget/EventEmitter

手写工具- 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 是用于浏览器事件用的,我们做些简化用以更适用日常代码:

  1. EventTarget 管理了多个事件,我们调整为针对单个事件,多个事件管理留给 EventEmitter
  2. 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++;
                }
            }
        }
    }
}

在上面代码中加了一些额外的概念

  1. 是否激活,可以在一些时候设置不触发事件
  2. 自动移除监听,处理事件函数返回 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 = {};
    }
}
  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值