事件匯流排是對發佈-訂閱模式的一種實現。 發佈-訂閱模式定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都將得到通知。 發佈-訂閱模式實現了松耦合,發佈者不是直接將消息發送給訂閱者,而是經過了一個中間的代理,事件匯流排就是一種中間代理的實現。 ...
事件匯流排與發佈訂閱模式
事件匯流排是對發佈-訂閱模式的一種實現。
發佈-訂閱模式定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都將得到通知。
發佈-訂閱模式實現了松耦合,發佈者不是直接將消息發送給訂閱者,而是經過了一個中間的代理,事件匯流排就是一種中間代理的實現。
事件匯流排維護了一個事件列表,訂閱者可以訂閱某一個事件,並指定一個回調(回調的具體實現在訂閱者內部);
每個事件又維護了一個依賴列表,發佈者可以“觸發”一個事件,事件匯流排負責遍歷該事件的依賴列表,調用每一個當初訂閱者訂閱時指定的回調函數。
在 JS 中實現Event Bus
定義一個EventBus類:
class EventBus{}
需要維護一個事件列表,在初始化事件匯流排對象的時候創建。
對於每一個事件,我們需要記錄它的事件名(string
類型),還需要記錄該事件的依賴列表(Array<Function>
類型),依賴列表其實就是各個訂閱者的回調函數的列表。
這採用了一個對象來記錄多個事件,剛好鍵值對就是事件名:依賴列表
constructor(){
this.eventObject = {};
}
實現訂閱:
每一次訂閱需要指定訂閱的事件名和發佈時要觸發的回調函數。
- 如果指定的事件不存在,則添加一個事件,並推入該新依賴(回調函數)。
- 如果指定的事件存在,則直接推入新依賴(回調函數)
subscribe(eventName, callback){
if(!this.eventObject[eventName]){
this.eventObject[eventName] = [];
}
this.eventObject[eventName].push(callback);
}
實現發佈:
發佈也要考慮到指定的事件是否存在。如果不存在,則中斷並返回警告;如果存在指定事件,則依次調用事件的依賴列表(回調列表)。
publish(eventName){
const callbackList = this.eventObject[eventName];
if(!callbackList)return console.warn(eventName + " Not Found!");
for(let callback of callbackList){
callback();
}
}
彙總如下:
class EventBus{
constructor(){
this.eventObject = {};
}
/**
* @param {string} eventName
*/
publish(eventName){
const callbackList = this.eventObject[eventName];
if(!callbackList)return console.warn(eventName + " Not Found!");
for(let callback of callbackList){
callback();
}
}
/**
* @param {string} eventName
* @param {Function} callback
*/
subscribe(eventName, callback){
if(!this.eventObject[eventName]){
this.eventObject[eventName] = [];
}
this.eventObject[eventName].push(callback);
}
}
優化Event Bus的實現
在發佈時傳遞參數
使用...args
介紹不定長參數列表,在發佈時傳入,併在調用回調函數列表的時候依次傳入。
/**
* @param {string} eventName
*/
publish(eventName, ...args){
const callbackList = this.eventObject[eventName];
if(!callbackList)return console.warn(eventName + " Not Found!");
for(let callback of callbackList){
callback(...args);
}
}
提供取消訂閱的操作
在訂閱者調用subscribe
方法訂閱事件的時候,返回一個用於取消訂閱的unSubscribe
方法。
在實現事件的回調函數列表的時候,需要為每一個回調函數添加一個id,方便以後查詢並刪除該回調函數。
這裡將訂閱的回調函數列表換成用對象結構存儲,因為在數組中刪除某個中間元素較麻煩且耗時,效率不如對象結構的delete
刪除鍵值對。
換成對象結構存儲後,鍵值對錶示:id:回調函數
。
class EventBus{
constructor(){
// 初始化事件列表
this.eventObject = {};
// 回調函數列表的 id
this.callbackId = 0;
}
/**
* @param {string} eventName
*/
publish(eventName, ...args){
// 取出該事件的回調函數列表(對象)
const callbackObject = this.eventObject[eventName];
if(!callbackObject)return console.warn(eventName + " Not Found!");
// 執行每一個回調函數,這裡的id是對象的key
for(let id in callbackObject){
callbackObject[id](...args);
}
}
/**
* @param {string} eventName
* @param {Function} callback
* @returns {{
* unSubscribe: Function
* }}
*/
subscribe(eventName, callback){
if(!this.eventObject[eventName]){
this.eventObject[eventName] = {};
}
// 為當前事務的回調函數申請一個專屬id
const id = this.callbackId++;
// 綁定回調函數
this.eventObject[eventName][id] = callback;
// 生成取消訂閱的函數
const unSubscribe = ()=>{
// 刪除該回調函數
delete this.eventObject[eventName][id];
// 如果該事件的回調函數都刪完了,則順便刪除事件列表中的事件
if(Object.keys(this.eventObject[eventName]).length === 0){
delete this.eventObject[eventName];
}
}
return {unSubscribe};
}
}
清除某個事件
/**
* @description 清除某事件
* @param {string} eventName
*/
clear(eventName){
if(!this.eventObject.hasOwnProperty(eventName)){
return console.warn(eventName + " Not Found!");
}
delete this.eventObject[eventName];
}