在以前兩篇自測清單中,和你們分享了不少 JavaScript 基礎知識,你們能夠一塊兒再回顧下~javascript
本文是我在咱們團隊內部「現代 JavaScript 突擊隊」分享的一篇內容,第二期學習內容爲「設計模式」系列,我會將我負責分享的知識整理成文章輸出,但願可以和你們一塊兒溫故知新!php
「現代 JavaScript 突擊隊」學習總結:前端
最近剛畢業的學生 Leo 準備開始租房了,他來到房產中介,跟中介描述了本身的租房需求,開開心心回家了。次日,中介的小哥哥小姐姐爲 Leo 列出符他需求的房間,並打電話約他一塊兒看房了,最後 Leo 選中一套滿意的房間,高高興興過去籤合同,準備開始新生活~vue
還有個大佬 Paul,準備將手中 10 套房出租出去,因而他來到房產中介,在中介那邊提供了本身要出租的房間信息,溝通好手續費,開開心心回家了。次日,Paul 接到中介的好消息,房子租出去了,因而他高高興興過去籤合同,開始收房租了~java
上面場景有個須要特別注意的地方:typescript
這兩點其實就是後面要介紹的 發佈-訂閱模式 的一個核心特色。編程
在軟件架構中,發佈-訂閱模式是一種消息範式,消息的發送者(稱爲發佈者)不會將消息直接發送給特定的接收者(稱爲訂閱者)。而是將發佈的消息分爲不一樣的類別,無需瞭解哪些訂閱者(若是有的話)可能存在。一樣的,訂閱者能夠表達對一個或多個類別的興趣,只接收感興趣的消息,無需瞭解哪些發佈者(若是有的話)存在。設計模式
發佈-訂閱是消息隊列範式的兄弟,一般是更大的面向消息中間件系統的一部分。大多數消息系統在API中同時支持消息隊列模型和發佈/訂閱模型,例如Java消息服務(JMS)。微信
這種模式提供了更大的網絡可擴展性和更動態的網絡拓撲,同時也下降了對發佈者和發佈數據的結構修改的靈活性。網絡
看完上面概念,有沒有以爲與觀察者模式很像?
但其實二者仍是有差別的,接下來一塊兒看看。
咱們分別爲經過兩種實際生活場景來介紹這兩種模式:
這兩種場景的過程分別是這樣:
觀察者模式中,消費顧客關注(如加微信好友)本身有興趣的微商,微商就會私聊發本身在賣的產品給消費顧客。
這個過程當中,消費顧客至關於觀察者(Observer),微商至關於觀察目標(Subject)。
接下來看看 發佈-訂閱模式 :
在 發佈-訂閱模式 中,消費顧客經過淘寶搜索本身關注的產品,商家經過淘寶發佈商品,當消費顧客在淘寶搜索的產品,已經有商家發佈,則淘寶會將對應商品推薦給消費顧客。
這個過程當中,消費顧客至關於訂閱者,淘寶至關於事件總線,商家至關於發佈者。
因此能夠看出,觀察者模式和發佈-訂閱模式差異在於有沒有一箇中央的事件總線。若是有,咱們就能夠認爲這是個發佈-訂閱模式。若是沒有,那麼就能夠認爲是觀察者模式。由於其實它們都實現了一個關鍵的功能:發佈事件-訂閱事件並觸發事件。
對比完觀察者模式和發佈-訂閱模式後,咱們大體理解發佈-訂閱模式是什麼了。接着總結下該模式的特色:
在發佈-訂閱模式中,一般包含如下角色:
發佈-訂閱模式能夠將衆多須要通訊的子系統(Subsystem)解耦,每一個子系統獨立管理。並且即便部分子系統取消訂閱,也不會影響事件總線的總體管理。
發佈-訂閱模式中每一個應用程序均可以專一於其核心功能,而事件總線負責將消息路由到每一個訂閱者手裏。
發佈-訂閱模式增長了系統的可伸縮性,提升了發佈者的響應能力。緣由是發佈者(Publisher)能夠快速地向輸入通道發送一條消息,而後返回到其核心處理職責,而沒必要等待子系統處理完成。而後事件總線負責確保把消息傳遞到每一個訂閱者(Subscriber)手裏。
發佈-訂閱模式提升了可靠性。異步的消息傳遞有助於應用程序在增長的負載下繼續平穩運行,而且能夠更有效地處理間歇性故障。
你不須要關心不一樣的組件是如何組合在一塊兒的,只要他們共同遵照一份協議便可。
發佈-訂閱模式容許延遲處理或者按計劃的處理。例如當系統負載大的時候,訂閱者能夠等到非高峯時間才接收消息,或者根據特定的計劃處理消息。
若是咱們項目中不多使用到訂閱者,或者與子系統實時交互較少,則不適合 發佈-訂閱模式 。
在如下狀況下能夠考慮使用此模式:
interface Publisher<T> { subscriber: string; data: T; } interface EventChannel<T> { on : (subscriber: string, callback: () => void) => void; off : (subscriber: string, callback: () => void) => void; emit: (subscriber: string, data: T) => void; } interface Subscriber { subscriber: string; callback: () => void; } // 方便後面使用 interface PublishData { [key: string]: string; }
class ConcretePublisher<T> implements Publisher<T> { public subscriber: string = ""; public data: T; constructor(subscriber: string, data: T) { this.subscriber = subscriber; this.data = data; } }
class ConcreteEventChannel<T> implements EventChannel<T> { // 初始化訂閱者對象 private subjects: { [key: string]: Function[] } = {}; // 實現添加訂閱事件 public on(subscriber: string, callback: () => void): void { console.log(`收到訂閱信息,訂閱事件:${subscriber}`); if (!this.subjects[subscriber]) { this.subjects[subscriber] = []; } this.subjects[subscriber].push(callback); }; // 實現取消訂閱事件 public off(subscriber: string, callback: () => void): void { console.log(`收到取消訂閱請求,須要取消的訂閱事件:${subscriber}`); if (callback === null) { this.subjects[subscriber] = []; } else { const index: number = this.subjects[subscriber].indexOf(callback); ~index && this.subjects[subscriber].splice(index, 1); } }; // 實現發佈訂閱事件 public emit (subscriber: string, data: T): void { console.log(`收到發佈者信息,執行訂閱事件:${subscriber}`); this.subjects[subscriber].forEach(item => item(data)); }; }
class ConcreteSubscriber implements Subscriber { public subscriber: string = ""; constructor(subscriber: string, callback: () => void) { this.subscriber = subscriber; this.callback = callback; } public callback(): void { }; }
interface Publisher<T> { subscriber: string; data: T; } interface EventChannel<T> { on : (subscriber: string, callback: () => void) => void; off : (subscriber: string, callback: () => void) => void; emit: (subscriber: string, data: T) => void; } interface Subscriber { subscriber: string; callback: () => void; } interface PublishData { [key: string]: string; } class ConcreteEventChannel<T> implements EventChannel<T> { // 初始化訂閱者對象 private subjects: { [key: string]: Function[] } = {}; // 實現添加訂閱事件 public on(subscriber: string, callback: () => void): void { console.log(`收到訂閱信息,訂閱事件:${subscriber}`); if (!this.subjects[subscriber]) { this.subjects[subscriber] = []; } this.subjects[subscriber].push(callback); }; // 實現取消訂閱事件 public off(subscriber: string, callback: () => void): void { console.log(`收到取消訂閱請求,須要取消的訂閱事件:${subscriber}`); if (callback === null) { this.subjects[subscriber] = []; } else { const index: number = this.subjects[subscriber].indexOf(callback); ~index && this.subjects[subscriber].splice(index, 1); } }; // 實現發佈訂閱事件 public emit (subscriber: string, data: T): void { console.log(`收到發佈者信息,執行訂閱事件:${subscriber}`); this.subjects[subscriber].forEach(item => item(data)); }; } class ConcretePublisher<T> implements Publisher<T> { public subscriber: string = ""; public data: T; constructor(subscriber: string, data: T) { this.subscriber = subscriber; this.data = data; } } class ConcreteSubscriber implements Subscriber { public subscriber: string = ""; constructor(subscriber: string, callback: () => void) { this.subscriber = subscriber; this.callback = callback; } public callback(): void { }; } /* 運行示例 */ const pingan8787 = new ConcreteSubscriber( "running", () => { console.log("訂閱者 pingan8787 訂閱事件成功!執行回調~"); } ); const leo = new ConcreteSubscriber( "swimming", () => { console.log("訂閱者 leo 訂閱事件成功!執行回調~"); } ); const lisa = new ConcreteSubscriber( "swimming", () => { console.log("訂閱者 lisa 訂閱事件成功!執行回調~"); } ); const pual = new ConcretePublisher<PublishData>( "swimming", {message: "pual 發佈消息~"} ); const eventBus = new ConcreteEventChannel<PublishData>(); eventBus.on(pingan8787.subscriber, pingan8787.callback); eventBus.on(leo.subscriber, leo.callback); eventBus.on(lisa.subscriber, lisa.callback); // 發佈者 pual 發佈 "swimming"相關的事件 eventBus.emit(pual.subscriber, pual.data); eventBus.off (lisa.subscriber, lisa.callback); eventBus.emit(pual.subscriber, pual.data); /* 輸出結果: [LOG]: 收到訂閱信息,訂閱事件:running [LOG]: 收到訂閱信息,訂閱事件:swimming [LOG]: 收到訂閱信息,訂閱事件:swimming [LOG]: 收到發佈者信息,執行訂閱事件:swimming [LOG]: 訂閱者 leo 訂閱事件成功!執行回調~ [LOG]: 訂閱者 lisa 訂閱事件成功!執行回調~ [LOG]: 收到取消訂閱請求,須要取消的訂閱事件:swimming [LOG]: 收到發佈者信息,執行訂閱事件:swimming [LOG]: 訂閱者 leo 訂閱事件成功!執行回調~ */
完整代碼以下:
interface Publisher { subscriber: string; data: any; } interface EventChannel { on : (subscriber: string, callback: () => void) => void; off : (subscriber: string, callback: () => void) => void; emit: (subscriber: string, data: any) => void; } interface Subscriber { subscriber: string; callback: () => void; } class ConcreteEventChannel implements EventChannel { // 初始化訂閱者對象 private subjects: { [key: string]: Function[] } = {}; // 實現添加訂閱事件 public on(subscriber: string, callback: () => void): void { console.log(`收到訂閱信息,訂閱事件:${subscriber}`); if (!this.subjects[subscriber]) { this.subjects[subscriber] = []; } this.subjects[subscriber].push(callback); }; // 實現取消訂閱事件 public off(subscriber: string, callback: () => void): void { console.log(`收到取消訂閱請求,須要取消的訂閱事件:${subscriber}`); if (callback === null) { this.subjects[subscriber] = []; } else { const index: number = this.subjects[subscriber].indexOf(callback); ~index && this.subjects[subscriber].splice(index, 1); } }; // 實現發佈訂閱事件 public emit (subscriber: string, data = null): void { console.log(`收到發佈者信息,執行訂閱事件:${subscriber}`); this.subjects[subscriber].forEach(item => item(data)); }; } class ConcretePublisher implements Publisher { public subscriber: string = ""; public data: any; constructor(subscriber: string, data: any) { this.subscriber = subscriber; this.data = data; } } class ConcreteSubscriber implements Subscriber { public subscriber: string = ""; constructor(subscriber: string, callback: () => void) { this.subscriber = subscriber; this.callback = callback; } public callback(): void { }; } /* 運行示例 */ const pingan8787 = new ConcreteSubscriber( "running", () => { console.log("訂閱者 pingan8787 訂閱事件成功!執行回調~"); } ); const leo = new ConcreteSubscriber( "swimming", () => { console.log("訂閱者 leo 訂閱事件成功!執行回調~"); } ); const lisa = new ConcreteSubscriber( "swimming", () => { console.log("訂閱者 lisa 訂閱事件成功!執行回調~"); } ); const pual = new ConcretePublisher( "swimming", {message: "pual 發佈消息~"} ); const eventBus = new ConcreteEventChannel(); eventBus.on(pingan8787.subscriber, pingan8787.callback); eventBus.on(leo.subscriber, leo.callback); eventBus.on(lisa.subscriber, lisa.callback); // 發佈者 pual 發佈 "swimming"相關的事件 eventBus.emit(pual.subscriber, pual.data); eventBus.off (lisa.subscriber, lisa.callback); eventBus.emit(pual.subscriber, pual.data); /* 輸出結果: [LOG]: 收到訂閱信息,訂閱事件:running [LOG]: 收到訂閱信息,訂閱事件:swimming [LOG]: 收到訂閱信息,訂閱事件:swimming [LOG]: 收到發佈者信息,執行訂閱事件:swimming [LOG]: 訂閱者 leo 訂閱事件成功!執行回調~ [LOG]: 訂閱者 lisa 訂閱事件成功!執行回調~ [LOG]: 收到取消訂閱請求,須要取消的訂閱事件:swimming [LOG]: 收到發佈者信息,執行訂閱事件:swimming [LOG]: 訂閱者 leo 訂閱事件成功!執行回調~ */
參考文章:《Vue事件總線(EventBus)使用詳細介紹》 。
在 Vue.js 中建立 EventBus 有兩種方式:
// event-bus.js import Vue from 'vue' export const EventBus = new Vue();
main.js
全局掛載 Vue 實例化的結果。// main.js Vue.prototype.$EventBus = new Vue()
假設你有兩個Vue頁面須要通訊: A 和 B ,A頁面按鈕上綁定了點擊事件,發送一則消息,通知 B 頁面。
<!-- A.vue --> <template> <button @click="sendMsg()">-</button> </template> <script> import { EventBus } from "../event-bus.js"; export default { methods: { sendMsg() { EventBus.$emit("aMsg", '來自A頁面的消息'); } } }; </script>
B 頁面中接收消息,並展現內容到頁面上。
<!-- IncrementCount.vue --> <template> <p>{{msg}}</p> </template> <script> import { EventBus } from "../event-bus.js"; export default { data(){ return { msg: '' } }, mounted() { EventBus.$on("aMsg", (msg) => { // A發送來的消息 this.msg = msg; }); } }; </script>
同理能夠從 B 頁面往 A 頁面發送消息,使用下面方法:
// 發送消息 EventBus.$emit(channel: string, callback(payload1,…)) // 監聽接收消息 EventBus.$on(channel: string, callback(payload1,…))
使用 EventBus.$off('aMsg')
來移除應用內全部對此某個事件的監聽。或者直接用 EventBus.$off()
來移除全部事件頻道,不須要添加任何參數 。
import { eventBus } from './event-bus.js' EventBus.$off('aMsg', {})
觀察者模式和發佈-訂閱模式的差異在於事件總線,若是有則是發佈-訂閱模式,反之爲觀察者模式。因此在實現發佈-訂閱模式,關鍵在於實現這個事件總線,在某個特定時間觸發某個特定事件,從而觸發監聽這個特定事件的組件進行相應操做的功能。發佈-訂閱模式在不少時候很是有用。