12 KiB
核心事件总线
**本文档引用的文件** - [EventBuilderImpl.ts](file://src/events/impl/EventBuilderImpl.ts) - [IEventBuilder.ts](file://src/events/IEventBuilder.ts) - [IDestroyable.ts](file://src/common/types/IDestroyable.ts) - [EventManager.ts](file://src/events/EventManager.ts) - [DesktopEventManager.ts](file://src/events/DesktopEventManager.ts) - [WindowFormEventManager.ts](file://src/events/WindowFormEventManager.ts)目录
简介
EventBuilderImpl 类是本系统事件机制的核心实现,提供了一个类型安全、功能完整的事件总线系统。该类实现了 IEventBuilder 接口,遵循发布-订阅模式,支持事件的注册、移除和触发,并具备立即执行、一次性监听等高级特性。通过继承 IDestroyable 接口,它还提供了资源清理能力,有效防止内存泄漏。
该事件系统被多个模块广泛使用,包括全局事件管理器(eventManager)、桌面事件管理器(desktopEM)和窗口表单事件管理器(wfem),构成了整个应用的通信骨架。
Section sources
核心数据结构设计
EventBuilderImpl 采用 Map 和 Set 的组合来高效管理事件监听器,这种设计在性能和内存使用上达到了良好的平衡。
其核心是一个私有成员 _eventHandlers,其类型为 Map<keyof Events, Set<HandlerWrapper<Events[keyof Events]>>>。这个数据结构的含义是:
- 外层 Map:以事件名称(
keyof Events)作为键,确保每个事件名对应一个独立的监听器集合。 - 内层 Set:存储特定事件的所有监听器包装对象(
HandlerWrapper)。使用Set而非数组可以天然避免重复添加同一个监听函数,并且插入和删除操作的时间复杂度为 O(1)。
HandlerWrapper 是一个简单的接口,包含两个属性:fn(原始的监听函数)和 once(布尔值,标记是否为一次性监听器)。这种包装方式将业务逻辑(函数本身)与元数据(如 once 标志)分离,使得事件管理更加灵活。
classDiagram
class EventBuilderImpl~Events~ {
-_eventHandlers : Map~keyof Events, Set~HandlerWrapper~Events[keyof Events]~~~
+addEventListener()
+removeEventListener()
+notifyEvent()
+destroy()
}
class HandlerWrapper~T~ {
+fn : T
+once : boolean
}
EventBuilderImpl --> "0..*" HandlerWrapper : 包含
Diagram sources
Section sources
泛型类型安全机制
EventBuilderImpl 类通过 TypeScript 的泛型系统实现了严格的类型安全。其定义为 class EventBuilderImpl<Events extends IEventMap>,其中 Events 是一个必须符合 IEventMap 接口约束的泛型类型。
IEventMap 接口定义了事件映射的基本结构:一个索引签名 [key: string]: (...args: any[]) => void,表示键是字符串类型的事件名,值是任意参数的无返回值函数。
当实例化 EventBuilderImpl 时,需要传入一个具体的事件接口。例如,在 WindowFormEventManager.ts 中定义了 IWindowFormEvent 接口,并将其作为泛型参数:
export const wfem = new EventBuilderImpl<IWindowFormEvent>()
这种设计带来了以下优势:
- 编译时检查:在调用
addEventListener或notifyEvent时,TypeScript 编译器会根据IWindowFormEvent的定义检查事件名和参数类型是否正确。 - 智能提示:开发者在编写代码时能获得准确的事件名和参数类型提示。
- 防止错误:无法订阅未在接口中定义的事件,也无法传递错误类型的参数。
Section sources
事件监听器管理
addEventListener 方法详解
addEventListener 方法用于向事件总线注册新的监听器。它接收三个参数:事件名、处理函数和可选的配置项。
配置项行为逻辑
- immediate (立即执行):如果设置为
true,则在添加监听器后立即同步执行一次该函数。 - immediateArgs (立即执行参数):为
immediate执行阶段提供参数。若未指定,则使用空数组。 - once (一次性监听):如果设置为
true,则该监听器在第一次被触发后自动从事件队列中移除。
实现细节
- 空值检查:首先检查
handler是否存在,避免无效监听器。 - 惰性初始化:如果这是该事件的第一个监听器,则创建一个新的
Set来存放后续的HandlerWrapper。 - 去重机制:在添加前,通过遍历
Set并比较wrapper.fn === handler来确保不会重复添加相同的函数引用。 - 异常捕获:在执行
immediate回调时,使用try-catch捕获任何可能抛出的异常,并将其输出到控制台,防止因单个监听器的错误而中断主流程。
flowchart TD
Start([开始]) --> CheckHandler["检查 handler 是否为空"]
CheckHandler --> |否| InitSet["检查事件对应的 Set 是否存在"]
InitSet --> |否| CreateSet["创建新的 Set"]
CreateSet --> GetSet["获取事件对应的 Set"]
InitSet --> |是| GetSet
GetSet --> CheckDuplicate["检查是否已存在相同 handler"]
CheckDuplicate --> |否| AddWrapper["添加 HandlerWrapper 到 Set"]
AddWrapper --> CheckImmediate["检查 immediate 选项"]
CheckImmediate --> |是| ExecuteNow["立即执行 handler"]
ExecuteNow --> End([结束])
CheckImmediate --> |否| End
CheckHandler --> |是| End
CheckDuplicate --> |是| End
Diagram sources
Section sources
removeEventListener 方法详解
removeEventListener 方法负责从事件队列中移除指定的监听器。
其实现的关键在于引用比对去重机制。由于监听器是以 HandlerWrapper 对象的形式存储在 Set 中的,直接比较 Set 中的对象与传入的 handler 函数是不相等的。因此,该方法会遍历 Set 中的每一个 wrapper,并使用 wrapper.fn === handler 来精确匹配原始的函数引用。
一旦找到匹配项,就调用 set.delete(wrapper) 将其从 Set 中移除。这种基于引用的比对确保了只有完全相同的函数实例才会被移除,保证了操作的准确性。
Section sources
事件通知与分发
notifyEvent 方法是事件系统的触发点,负责通知所有订阅了特定事件的监听器。
其工作流程如下:
- 存在性检查:首先检查是否存在该事件名对应的监听器集合,若不存在则直接返回。
- 批量通知:遍历该事件名对应
Set中的所有HandlerWrapper。 - 异常捕获:在调用每个监听器的
fn(...args)时,使用try-catch块包裹,确保单个监听器的错误不会影响其他监听器的执行。 - once 监听器清理:在成功调用一个监听器后,检查其
once标志。如果为true,则立即将其从Set中删除,实现一次性监听的功能。
这种“先通知,后清理”的策略保证了即使在 once 监听器执行过程中有其他代码尝试移除它,也不会产生竞态条件。
sequenceDiagram
participant Publisher as 事件发布者
participant EventBus as EventBuilderImpl
participant ListenerA as 监听器 A (once : true)
participant ListenerB as 监听器 B (once : false)
Publisher->>EventBus : notifyEvent('click', x, y)
EventBus->>ListenerA : 执行 fn(x, y)
ListenerA-->>EventBus : 完成
EventBus->>EventBus : 检查 wrapper.once == true
EventBus->>EventBus : 从 Set 中删除 ListenerA
EventBus->>ListenerB : 执行 fn(x, y)
ListenerB-->>EventBus : 完成
EventBus-->>Publisher : 通知完成
Diagram sources
Section sources
资源清理与内存泄漏防范
EventBuilderImpl 通过多种策略有效防范内存泄漏:
-
显式销毁接口:通过实现
IDestroyable接口,提供了destroy()方法。调用此方法会清空_eventHandlersMap,释放所有对监听器函数的引用,使它们可以被垃圾回收器回收。这对于长生命周期的应用或动态创建/销毁的组件至关重要。 -
监听器去重:
addEventListener方法中的去重逻辑防止了同一函数被多次添加,避免了不必要的内存占用和重复执行。 -
once 监听器自动清理:
once选项确保了一次性监听器在执行后立即被移除,无需手动清理。 -
异常隔离:
try-catch机制保证了事件分发过程的健壮性,防止因监听器内部错误导致整个事件系统崩溃。
Section sources
实际应用示例
以下是 EventBuilderImpl 在项目中的具体应用实例:
- 全局事件管理器:在
EventManager.ts中,eventManager实例被用来管理认证状态改变 (onAuthChange) 和主题切换 (onThemeChange) 等全局事件。 - 桌面事件管理器:在
DesktopEventManager.ts中,desktopEM实例用于响应桌面应用图标位置的变化 (desktopAppPosChange)。 - 窗口表单事件管理器:在
WindowFormEventManager.ts中,wfem实例监控窗口的最小化、最大化、关闭等生命周期事件。
这些预定义的事件管理器实例化了 EventBuilderImpl,并通过具体的事件接口(如 IWindowFormEvent)锁定了可用的事件集,为不同模块提供了清晰、类型安全的通信契约。
Section sources
总结
EventBuilderImpl 作为一个轻量级但功能完备的事件总线实现,通过精心设计的数据结构(Map+Set)和 TypeScript 泛型,实现了高性能、类型安全的事件管理。其 addEventListener、removeEventListener 和 notifyEvent 方法构成了一个健壮的发布-订阅循环,而 immediate、once 等选项以及异常捕获机制则增强了其实用性和可靠性。最后,通过 IDestroyable 接口提供的 destroy 方法,确保了资源的可管理性,有效防止了内存泄漏,是构建可维护前端应用的理想选择。