發布訂閱模式的TS實現
簡介
發布訂閱模式是一種常用的用于解耦的模式。
它和觀察者模式的區別在于:
- 觀察者模式:被觀察者需要維護一個觀察者的集合;
- 發布訂閱模式:通信雙方互相不知道對方的存在,通過第三方事件總線進行通信。
發布訂閱模式在前端領域很常見,例如:
- Vue 框架中組件的
$on和$emit方法; - Node.js 中 EventEmitter 中的
on和emit方法。
圖示:
-
訂閱者通過
on方法注冊事件:
-
發布者通過
emit觸發回調列表:
發布者和訂閱者雙方不知道各自的存在,它們僅通過
Event Bus進行通信。
實現
事件總線最基本的兩個方法是 on 和 emit 。
常見的設計還有兩個方法是 off 和 once,off 用于注銷事件,once 是 on 的特例,表示僅訂閱一次。
函數簽名
type CallbackFn = (...args: any[]) => void;
on(eventName: string, callback: CallbackFn): void;
emit(enentName: string, ...args: any): void;
off(eventName: string, callback: CallbackFn): void;
once(eventName: string, callback: CallbackFn): void;
實現思路
在事件總線中需要建立起事件名到回調集合的映射:
- 當
on時,將回調添加到指定事件名的回調集合中; - 當
emit時,遍歷指定時間名的回調集合,依次執行其中的回調函數;
回調集合可以使用Set實現,也可以使用數組實現。
由于on的實現需要做去重,建議使用Set,比較方便。
once的實現:
once 可以基于 on 和 off 實現,先使用 on 注冊,執行回調之后就執行 off 注銷,從而實現僅觸發一次。
代碼
使用 TypeScript 實現。
首先聲明一個回調函數的類型,簡化后續代碼:
type CallbackFn = (...args: any[]) => void;
然后是聲明 EventBus 類,成員屬性中使用對象建立起 “事件名與回調集合” 的映射關系:
class EventBus{
events: Record<string, Set<CallbackFn>> = {};
constructor(){}
on(eventName: string, callback: CallbackFn){ /* ... */ }
emit(eventName: string, ...args: any[]){/* ... */}
off(eventName: string, callback: CallbackFn){/* ... */}
once(eventName: string, callback: CallbackFn){/* ... */}
}
on
on(eventName: string, callback: CallbackFn){
if(!this.events[eventName]){
this.events[eventName] = new Set();
}
this.events[eventName].add(callback);
}
- 如果事件名不存在,則要初始化創建一個 Set 用于記錄。
- 如果事件名存在,則直接將回調添加到 Set 中。
這段代碼可以通過 短路運算符 簡化:
on(eventName: string, callback: CallbackFn){
(this.events[eventName] ??= new Set()).add(callback);
}
emit
遍歷回調集合就??了。(記得帶上函數參數)
emit(eventName: string, ...args: any[]){
this.events[eventName]?.forEach(cb => cb(...args));
}
off
注銷事件也很簡單,使用 Set 的 delete 方法就可以了。
off(eventName: string, callback: CallbackFn){
this.events[eventName]?.delete(callback);
}
once
對 once 傳入的回調函數做一層包裝,在執行之后調用 off 注銷事件。
使用箭頭函數是為了 this 指向正確。使用 function 和 bind 也??,箭頭函數比較簡潔。
once(eventName: string, callback: CallbackFn){
const handler = (...args: any[]) => {
callback(...args);
this.off(eventName, handler);
}
this.on(eventName, handler);
}
代碼匯總:
type CallbackFn = (...args: any[]) => void;
class EventBus{
events: Record<string, Set<CallbackFn>> = {};
constructor(){}
on(eventName: string, callback: CallbackFn){
(this.events[eventName] ??= new Set()).add(callback);
}
emit(eventName: string, ...args: any[]){
this.events[eventName]?.forEach(cb => cb(...args));
}
off(eventName: string, callback: CallbackFn){
this.events[eventName]?.delete(callback);
}
once(eventName: string, callback: CallbackFn){
const handler = (...args: any[]) => {
callback(...args);
this.off(eventName, handler);
}
this.on(eventName, handler);
}
}

浙公網安備 33010602011771號