<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
Event Bus
事件匯流排,通常作為多個模組間的通訊機制,相當於一個事件管理中心,一個模組傳送訊息,其它模組接受訊息,就達到了通訊的作用。
比如,Vue 元件間的資料傳遞可以使用一個 Event Bus
來通訊,也可以用作微核心外掛系統中的外掛和核心通訊。
Event Bus
本質上是採用了釋出-訂閱的設計模式,比如多個模組 A
、B
、C
訂閱了一個事件 EventX
,然後某一個模組 X
在事件匯流排釋出了這個事件,那麼事件匯流排會負責通知所有訂閱者 A
、B
、C
,它們都能收到這個通知訊息,同時還可以傳遞引數。
如何使用 JavaScript 來實現一個簡單版本的 Event Bus
EventBus
類,初始化一個空物件用於存放所有的事件以下是程式碼詳細實現,可以複製到谷歌瀏覽器控制檯直接執行檢測效果。
程式碼
class EventBus { constructor() { // 初始化事件列表 this.eventObject = {}; } // 釋出事件 publish(eventName) { // 取出當前事件所有的回撥函數 const callbackList = this.eventObject[eventName]; if (!callbackList) return console.warn(eventName + " not found!"); // 執行每一個回撥函數 for (let callback of callbackList) { callback(); } } // 訂閱事件 subscribe(eventName, callback) { // 初始化這個事件 if (!this.eventObject[eventName]) { this.eventObject[eventName] = []; } // 儲存訂閱者的回撥函數 this.eventObject[eventName].push(callback); } } // 測試 const eventBus = new EventBus(); // 訂閱事件eventX eventBus.subscribe("eventX", () => { console.log("模組A"); }); eventBus.subscribe("eventX", () => { console.log("模組B"); }); eventBus.subscribe("eventX", () => { console.log("模組C"); }); // 釋出事件eventX eventBus.publish("eventX"); // 輸出 > 模組A > 模組B > 模組C
上面我們實現了最基礎的釋出和訂閱功能,實際應用中,還可能有更進階的需求。
釋出者傳入一個引數到 EventBus
中,在 callback
回撥函數執行的時候接著傳出引數,這樣每一個訂閱者就可以收到引數了。
程式碼
class EventBus { constructor() { // 初始化事件列表 this.eventObject = {}; } // 釋出事件 publish(eventName, ...args) { // 取出當前事件所有的回撥函數 const callbackList = this.eventObject[eventName]; if (!callbackList) return console.warn(eventName + " not found!"); // 執行每一個回撥函數 for (let callback of callbackList) { // 執行時傳入引數 callback(...args); } } // 訂閱事件 subscribe(eventName, callback) { // 初始化這個事件 if (!this.eventObject[eventName]) { this.eventObject[eventName] = []; } // 儲存訂閱者的回撥函數 this.eventObject[eventName].push(callback); } } // 測試 const eventBus = new EventBus(); // 訂閱事件eventX eventBus.subscribe("eventX", (obj, num) => { console.log("模組A", obj, num); }); eventBus.subscribe("eventX", (obj, num) => { console.log("模組B", obj, num); }); eventBus.subscribe("eventX", (obj, num) => { console.log("模組C", obj, num); }); // 釋出事件eventX eventBus.publish("eventX", { msg: "EventX published!" }, 1); // 輸出 > 模組A {msg: 'EventX published!'} 1 > 模組B {msg: 'EventX published!'} 1 > 模組C {msg: 'EventX published!'} 1
有時候訂閱者只想在某一個時間段訂閱訊息,這就涉及帶取消訂閱功能。我們將對程式碼進行改造。
首先,要實現指定訂閱者取消訂閱,每一次訂閱事件時,都生成唯一一個取消訂閱的函數,使用者直接呼叫這個函數,我們就把當前訂閱的回撥函數刪除。
// 每一次訂閱事件,都生成唯一一個取消訂閱的函數 const unSubscribe = () => { // 清除這個訂閱者的回撥函數 delete this.eventObject[eventName][id]; };
其次,訂閱的回撥函數列表使換成物件結構儲存,為每一個回撥函數設定一個唯一 id
, 登出回撥函數的時候可以提高刪除的效率,如果還是使用陣列的話需要使用 split
刪除,效率不如物件的 delete
。
程式碼
class EventBus { constructor() { // 初始化事件列表 this.eventObject = {}; // 回撥函數列表的id this.callbackId = 0; } // 釋出事件 publish(eventName, ...args) { // 取出當前事件所有的回撥函數 const callbackObject = this.eventObject[eventName]; if (!callbackObject) return console.warn(eventName + " not found!"); // 執行每一個回撥函數 for (let id in callbackObject) { // 執行時傳入引數 callbackObject[id](...args); } } // 訂閱事件 subscribe(eventName, callback) { // 初始化這個事件 if (!this.eventObject[eventName]) { // 使用物件儲存,登出回撥函數的時候提高刪除的效率 this.eventObject[eventName] = {}; } const id = this.callbackId++; // 儲存訂閱者的回撥函數 // 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 }; } } // 測試 const eventBus = new EventBus(); // 訂閱事件eventX eventBus.subscribe("eventX", (obj, num) => { console.log("模組A", obj, num); }); eventBus.subscribe("eventX", (obj, num) => { console.log("模組B", obj, num); }); const subscriberC = eventBus.subscribe("eventX", (obj, num) => { console.log("模組C", obj, num); }); // 釋出事件eventX eventBus.publish("eventX", { msg: "EventX published!" }, 1); // 模組C取消訂閱 subscriberC.unSubscribe(); // 再次釋出事件eventX,模組C不會再收到訊息了 eventBus.publish("eventX", { msg: "EventX published again!" }, 2); // 輸出 > 模組A {msg: 'EventX published!'} 1 > 模組B {msg: 'EventX published!'} 1 > 模組C {msg: 'EventX published!'} 1 > 模組A {msg: 'EventX published again!'} 2 > 模組B {msg: 'EventX published again!'} 2
如果一個事件只發生一次,通常也只需要訂閱一次,收到訊息後就不用再接受訊息。
首先,我們提供一個 subscribeOnce
的介面,內部實現幾乎和 subscribe
一樣,只有一個地方有區別,在 callbackId
前面的加一個字元 d
,用來標示這是一個需要刪除的訂閱。
// 標示為只訂閱一次的回撥函數 const id = "d" + this.callbackId++;
然後,在執行回撥函數後判斷當前回撥函數的 id
有沒有標示,決定我們是否需要刪除這個回撥函數。
// 只訂閱一次的回撥函數需要刪除 if (id[0] === "d") { delete callbackObject[id]; }
程式碼
class EventBus { constructor() { // 初始化事件列表 this.eventObject = {}; // 回撥函數列表的id this.callbackId = 0; } // 釋出事件 publish(eventName, ...args) { // 取出當前事件所有的回撥函數 const callbackObject = this.eventObject[eventName]; if (!callbackObject) return console.warn(eventName + " not found!"); // 執行每一個回撥函數 for (let id in callbackObject) { // 執行時傳入引數 callbackObject[id](...args); // 只訂閱一次的回撥函數需要刪除 if (id[0] === "d") { delete callbackObject[id]; } } } // 訂閱事件 subscribe(eventName, callback) { // 初始化這個事件 if (!this.eventObject[eventName]) { // 使用物件儲存,登出回撥函數的時候提高刪除的效率 this.eventObject[eventName] = {}; } const id = this.callbackId++; // 儲存訂閱者的回撥函數 // 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 }; } // 只訂閱一次 subscribeOnce(eventName, callback) { // 初始化這個事件 if (!this.eventObject[eventName]) { // 使用物件儲存,登出回撥函數的時候提高刪除的效率 this.eventObject[eventName] = {}; } // 標示為只訂閱一次的回撥函數 const id = "d" + this.callbackId++; // 儲存訂閱者的回撥函數 // 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 }; } } // 測試 const eventBus = new EventBus(); // 訂閱事件eventX eventBus.subscribe("eventX", (obj, num) => { console.log("模組A", obj, num); }); eventBus.subscribeOnce("eventX", (obj, num) => { console.log("模組B", obj, num); }); eventBus.subscribe("eventX", (obj, num) => { console.log("模組C", obj, num); }); // 釋出事件eventX eventBus.publish("eventX", { msg: "EventX published!" }, 1); // 再次釋出事件eventX,模組B只訂閱了一次,不會再收到訊息了 eventBus.publish("eventX", { msg: "EventX published again!" }, 2); // 輸出 > 模組A {msg: 'EventX published!'} 1 > 模組C {msg: 'EventX published!'} 1 > 模組B {msg: 'EventX published!'} 1 > 模組A {msg: 'EventX published again!'} 2 > 模組C {msg: 'EventX published again!'} 2
我們還希望通過一個 clear
的操作來將指定事件的所有訂閱清除掉,這個通常在一些元件或者模組解除安裝的時候用到。
// 清除事件 clear(eventName) { // 未提供事件名稱,預設清除所有事件 if (!eventName) { this.eventObject = {}; return; } // 清除指定事件 delete this.eventObject[eventName]; }
和取消訂閱的邏輯相似,只不過這裡統一處理了。
程式碼
class EventBus { constructor() { // 初始化事件列表 this.eventObject = {}; // 回撥函數列表的id this.callbackId = 0; } // 釋出事件 publish(eventName, ...args) { // 取出當前事件所有的回撥函數 const callbackObject = this.eventObject[eventName]; if (!callbackObject) return console.warn(eventName + " not found!"); // 執行每一個回撥函數 for (let id in callbackObject) { // 執行時傳入引數 callbackObject[id](...args); // 只訂閱一次的回撥函數需要刪除 if (id[0] === "d") { delete callbackObject[id]; } } } // 訂閱事件 subscribe(eventName, callback) { // 初始化這個事件 if (!this.eventObject[eventName]) { // 使用物件儲存,登出回撥函數的時候提高刪除的效率 this.eventObject[eventName] = {}; } const id = this.callbackId++; // 儲存訂閱者的回撥函數 // 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 }; } // 只訂閱一次 subscribeOnce(eventName, callback) { // 初始化這個事件 if (!this.eventObject[eventName]) { // 使用物件儲存,登出回撥函數的時候提高刪除的效率 this.eventObject[eventName] = {}; } // 標示為只訂閱一次的回撥函數 const id = "d" + this.callbackId++; // 儲存訂閱者的回撥函數 // 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 }; } // 清除事件 clear(eventName) { // 未提供事件名稱,預設清除所有事件 if (!eventName) { this.eventObject = {}; return; } // 清除指定事件 delete this.eventObject[eventName]; } } // 測試 const eventBus = new EventBus(); // 訂閱事件eventX eventBus.subscribe("eventX", (obj, num) => { console.log("模組A", obj, num); }); eventBus.subscribe("eventX", (obj, num) => { console.log("模組B", obj, num); }); eventBus.subscribe("eventX", (obj, num) => { console.log("模組C", obj, num); }); // 釋出事件eventX eventBus.publish("eventX", { msg: "EventX published!" }, 1); // 清除 eventBus.clear("eventX"); // 再次釋出事件eventX,由於已經清除,所有模組都不會再收到訊息了 eventBus.publish("eventX", { msg: "EventX published again!" }, 2); // 輸出 > 模組A {msg: 'EventX published!'} 1 > 模組B {msg: 'EventX published!'} 1 > 模組C {msg: 'EventX published!'} 1 > eventX not found!
鑑於現在 TypeScript 已經被大規模採用,尤其是大型前端專案,我們簡要的改造為一個 TypeScript 版本
可以複製以下程式碼到 TypeScript Playground 體驗執行效果
程式碼
interface ICallbackList { [id: string]: Function; } interface IEventObject { [eventName: string]: ICallbackList; } interface ISubscribe { unSubscribe: () => void; } interface IEventBus { publish<T extends any[]>(eventName: string, ...args: T): void; subscribe(eventName: string, callback: Function): ISubscribe; subscribeOnce(eventName: string, callback: Function): ISubscribe; clear(eventName: string): void; } class EventBus implements IEventBus { private _eventObject: IEventObject; private _callbackId: number; constructor() { // 初始化事件列表 this._eventObject = {}; // 回撥函數列表的id this._callbackId = 0; } // 釋出事件 publish<T extends any[]>(eventName: string, ...args: T): void { // 取出當前事件所有的回撥函數 const callbackObject = this._eventObject[eventName]; if (!callbackObject) return console.warn(eventName + " not found!"); // 執行每一個回撥函數 for (let id in callbackObject) { // 執行時傳入引數 callbackObject[id](...args); // 只訂閱一次的回撥函數需要刪除 if (id[0] === "d") { delete callbackObject[id]; } } } // 訂閱事件 subscribe(eventName: string, callback: Function): ISubscribe { // 初始化這個事件 if (!this._eventObject[eventName]) { // 使用物件儲存,登出回撥函數的時候提高刪除的效率 this._eventObject[eventName] = {}; } const id = this._callbackId++; // 儲存訂閱者的回撥函數 // 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 }; } // 只訂閱一次 subscribeOnce(eventName: string, callback: Function): ISubscribe { // 初始化這個事件 if (!this._eventObject[eventName]) { // 使用物件儲存,登出回撥函數的時候提高刪除的效率 this._eventObject[eventName] = {}; } // 標示為只訂閱一次的回撥函數 const id = "d" + this._callbackId++; // 儲存訂閱者的回撥函數 // 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 }; } // 清除事件 clear(eventName: string): void { // 未提供事件名稱,預設清除所有事件 if (!eventName) { this._eventObject = {}; return; } // 清除指定事件 delete this._eventObject[eventName]; } } // 測試 interface IObj { msg: string; } type PublishType = [IObj, number]; const eventBus = new EventBus(); // 訂閱事件eventX eventBus.subscribe("eventX", (obj: IObj, num: number, s: string) => { console.log("模組A", obj, num); }); eventBus.subscribe("eventX", (obj: IObj, num: number) => { console.log("模組B", obj, num); }); eventBus.subscribe("eventX", (obj: IObj, num: number) => { console.log("模組C", obj, num); }); // 釋出事件eventX eventBus.publish("eventX", { msg: "EventX published!" }, 1); // 清除 eventBus.clear("eventX"); // 再次釋出事件eventX,由於已經清除,所有模組都不會再收到訊息了 eventBus.publish<PublishType>("eventX", { msg: "EventX published again!" }, 2); // 輸出 [LOG]: "模組A", { "msg": "EventX published!" }, 1 [LOG]: "模組B", { "msg": "EventX published!" }, 1 [LOG]: "模組C", { "msg": "EventX published!" }, 1 [WRN]: "eventX not found!"
在實際使用過程中,往往只需要一個事件匯流排就能滿足需求,這裡有兩種情況,保持在上層範例中單例和全域性單例。
1.保持在上層範例中單例
將事件匯流排引入到上層範例使用,只需要保證在一個上層範例中只有一個 EventBus
,如果上層範例有多個,意味著有多個事件匯流排,但是每個上層範例管控自己的事件匯流排。
首先在上層範例中建立一個變數用來儲存事件匯流排,只在第一次使用時初始化,後續其他模組使用事件匯流排時直接取得這個事件匯流排範例。
程式碼
// 上層範例 class LWebApp { private _eventBus?: EventBus; constructor() {} public getEventBus() { // 第一次初始化 if (this._eventBus == undefined) { this._eventBus = new EventBus(); } // 後續每次直接取唯一一個範例,保持在LWebApp範例中單例 return this._eventBus; } } // 使用 const eventBus = new LWebApp().getEventBus();
2.全域性單例
有時候我們希望不管哪一個模組想使用我們的事件匯流排,我們都想這些模組使用的是同一個範例,這就是全域性單例,這種設計能更容易統一管理事件。
寫法同上面的類似,區別是要把 _eventBus
和 getEventBus
轉為靜態屬性。使用時無需範例化 EventBusTool
工具類,直接使用靜態方法就行了。
程式碼
// 上層範例 class EventBusTool { private static _eventBus?: EventBus; constructor() {} public static getEventBus(): EventBus { // 第一次初始化 if (this._eventBus == undefined) { this._eventBus = new EventBus(); } // 後續每次直接取唯一一個範例,保持全域性單例 return this._eventBus; } } // 使用 const eventBus = EventBusTool.getEventBus();
以上是小編對 Event Bus
的一些理解,基本上實現了想要的效果。通過自己動手實現一遍釋出訂閱模式,也加深了對經典設計模式的理解。其中還有很多不足和需要優化的地方。
到此這篇關於JavaScript實現事件匯流排(Event Bus)的方法詳解的文章就介紹到這了,更多相關JavaScript事件匯流排Event Bus內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45