我用typescript来实现了一套事件系统

744 阅读4分钟

最近因为在开发一款设计器,需要自己实现一套事件系统,项目基于TypeScript实现,其包含事件的添加、删除、触发等功能。 目前主要采用了观察者模式,以下是基本的实现思路:

基于Map的事件调度存储

我使用Map来存储事件名称和回调函数数组的映射,以便快速查找指定事件名称对应的回调函数。在添加事件监听时,会先检查该事件是否已经存在,如果存在则直接将回调推入数组,如果不存在则先创建数组再推入。删除事件监听时,需要先在数组中找到对应的回调索引,然后进行splice删除。

触发方式

对于事件的触发,提供了三种触发方式:

  • emitEvent,这是一种异步触发方式,会通过Promise包裹,确保所有的回调执行完成。

  • emitEventSync,这是一种同步触发方式,会直接循环调用回调函数。

  • emitEventIterable,这是一种利用迭代器实现的可阻断同步触发方式。通过yield关键字可以返回结果控制后续回调是否继续。

使用上,首先需要通过addEventListener添加事件监听,然后通过emitEvent、emitEventSync或emitEventIterable触发事件并传入参数。当不需要某事件时,可以通过removeEventListener移除监听。

调用示例

自定义事件

当一个页面内容发生变化时候,我们需要定义一个事件监听机制,并在执行updatePageData逻辑后通知到每一个订阅事件的模块。 这里要注意的是代码中的this.app.facade.event=new Event(); ,这是我设计的系统在引导启动过程中注册到facade上的一个provider ,有兴趣的同学可以了解一下服务提供者以及门脸模式,而在这个事件系统的实现里,我们主要参考了观察者模式,他们所解决的问题不同:

  • Facade模式致力于简化复杂系统的外部 API 设计。
  • Provider模式旨在实现服务的解耦与替换。
  • Observer关注的是对象间的观察-通知关系,它通过订阅-发布机制实现了对象间的联动。

class Page extends BaseApi {
    //定义一个基础的事件常量,用于标记事件类型
    public static readonly EVENT_ON_CHANGE = 'pageOnChangeEvent';
    //实现自定义订阅方法
    public onChange(handler: (params: PageInfo[]) => void) {

        this.app.facade.event.addEventListener(Page.EVENT_ON_CHANGE, handler);

    }
    //更新页面数据后并发送事件通知
    public updatePageData() {
        //todo:修复并保存
        //通知
        this.app.facade.event.emitEvent(Page.EVENT_ON_CHANGE, this.pages);
    }
}

可阻断的事件

通过yield和return false可以实现一些比较复杂的控制流,这在一些需要顺序执行或条件触发的场景下很有用,比如说在页面跳转的时候进行拦截权限的动作,当没有权限的时候,我们ruturn false;即可触发中断后续事件通知。

//这是一个利用迭代器实现的可阻断的同步的事件触发器
emitEventIterable(eventName: string, ...args: any[]): boolean {
    if (this._events.has(eventName)) {
        const callbacks = this._events.get(eventName) || [];
        const eventIterable = function* () {
            for (let i = 0; i < callbacks.length; i++) {
                yield callbacks[i](...args); 
            }
        }
        const events = eventIterable()
        while (true) {
            //通过迭代器的方式保证执行顺序,并保证可阻断事件继续向后传播
            const result = events.next();

            if (result.value === false) {
                return false;
            }
            if (result.done) {
                return true;
            }
        }

    }
    return true;
}

完整代码

接口定义

我定义了一个IEvent接口,它包含事件系统需要实现的基本方法,如获取所有事件、获取某个事件的回调函数数组、添加和删除事件监听器等。在EventListener类中实现了该接口。

interface IEvent {
    _events: Map<string, Function[]>;


    getEvents(): Map<string, Function[]>;

    getEvent(eventName: string): Function[];

    removeEvent(eventName: string): void;

    addEventListener(eventName: string, callback: Function): void;


    removeEventListener(eventName: string, callback: Function): void;

    //这是一个异步的事件触发器
    emitEvent(eventName: string, ...args: any[]): void;

    //这是一个同步的事件触发器
    emitEventSync(eventName: string, ...args: any[]): void;

    //这是一个可阻断的同步的事件迭代触发器
    emitEventIterable(eventName: string, ...args: any[]): boolean;
}

export default IEvent;

完整实现

这个类,您可以理解为事件系统的中央总线,它可以在不同的组件之间传递数据和触发事件,实现组件之间的交互和数据共享。中央总线可以被看作是一个中间人,负责接收和分发消息。这里要注意的是,我并没有把Subject进行分离,所以这并不是一个标准的Observer,但实现的思路基本是一致的,因为已经具备了所有的要素。

import IEvent from "./iEvent.ts";

class Event implements IEvent {
    _events: Map<string, Function[]> = new Map();


    getEvents(): Map<string, Function[]> {
        return this._events;
    }

    getEvent(eventName: string): Function[] {
        return this._events.get(eventName)!;
    }

    removeEvent(eventName: string) {
        this._events.delete(eventName);
    }

    addEventListener(eventName: string, callback: Function) {
        if (this._events.has(eventName)) {
            this.removeEventListener(eventName, callback);
            this._events.get(eventName)!.push(callback);
        } else {
            this._events.set(eventName, [callback]);
        }
    }


    removeEventListener(eventName: string, callback: Function) {
        if (this._events.has(eventName)) {
            const callbacks = this._events.get(eventName)!;
            const index = callbacks.indexOf(callback);
            if (index !== -1) {
                callbacks.splice(index, 1);
            }
        }
    }

    //这是一个异步的事件触发器
    emitEvent(eventName: string, ...args: any[]): void {
        if (this._events.has(eventName)) {
            const callbacks = this._events.get(eventName) || [];
            new Promise((resolve) => {
                callbacks.forEach((callback) => {
                    callback(...args)
                })
                resolve(void 0);
            }).then(() => {
            }).catch((e) => {
                console.error(e);
            })
        }
    }

    //这是一个同步的事件触发器
    emitEventSync(eventName: string, ...args: any[]): void {
        if (this._events.has(eventName)) {
            const callbacks = this._events.get(eventName) || [];
            for (let i = 0; i < callbacks.length; i++) {
                callbacks[i](...args)
            }
        }
    }

    //这是一个利用迭代器实现的可阻断的同步的事件触发器
    emitEventIterable(eventName: string, ...args: any[]): boolean {
        console.log('emitEventIterable', eventName, this._events.get(eventName))
        if (this._events.has(eventName)) {
            const callbacks = this._events.get(eventName) || [];
            const eventIterable = function* () {
                for (let i = 0; i < callbacks.length; i++) {
                    yield callbacks[i](...args);
                }
            }
            const events = eventIterable()
            while (true) {
                const result = events.next();

                if (result.value === false) {
                    return false;
                }
                if (result.done) {
                    return true;
                }
            }

        }
        return true;
    }
}

export default Event;