發佈-訂閱設計模式對你們來講並非很陌生,舉一個最簡單的例子,在前端開發過程當中,事件的綁定就是其實際的應用。首先咱們先了解下什麼是發佈-訂閱模式。前端
基本概念:發佈-訂閱模式又叫觀察者模式,它定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴它的對象都獲得通知。在前端開發中,咱們通常用事件模型來替代傳統的發佈-訂閱模式。編程
發佈-訂閱模式是前端經常使用的一種設計模式,如今主流的MVVM框架,都大量使用了此設計模式,其主要做用有如下兩點:一是能夠實現模塊間通訊,而是能夠在必定程度上實現異步編程。其基本事件模型以下:設計模式
前端的事件綁定有三要素,一是傳入事件類型,二是聲明對應的回調方法,三是觸發條件;觸發條件爲對應的事件類型。前端DOM的事件系統本質也是發佈-訂閱模式,而咱們在業務處理中所應有的模式也與此相似,只不過發佈訂閱模式應用的是自定義事件類型,能夠自定義。緩存
發佈訂閱事件模型本質上就是一個中央事件處理總線,它接收全部訂閱的自定義事件,並將自定義事件的回調存儲到事件回調的堆棧中,當某在某個時刻觸發了此自定義事件,會調用分發事件的方法,從事件回調堆棧中取出符合條件的事件回調,依次調用,具體實現邏輯以下:app
class EventEmiter { constructor() { //回調中心 this.listenerList = {}; this.createEventList = {}; } /** * 添加事件監聽 * @param {事件類型} type * @param {回調方法} fn */ on(type, fn) { if (!this.listenerList[type]) { this.listenerList[type] = []; } this.listenerList[type].push(fn); } /** * 觸發事件監聽 * @param {事件類型} type */ emit(type, flag) { let fnList = this.listenerList[type]; let params = Array.from(arguments); if (!fnList || fnList.length === 0) { return false; } else { fnList.forEach(fn => { fn.apply(this, params.slice(1)); }); } } /** * 移除事件監聽 * @param {事件類型} type * @param {回調方法} fn */ off(type, fn) { if (!this.listenerList[type]) { return false; } else { let index = this.listenerList[type].findIndex(vv => vv === fn); if (index === -1) { return false; } else { this.listenerList[type].splice(index, 1); } } } } let eventBus = new EventEmiter(); function cb(param) { console.log("this is a test", param); } eventBus.on("test", cb); eventBus.emit("test", 123); eventBus.off("test", cb); eventBus.emit("test", 456);
以上只是對發佈訂閱模式進行一個簡單的實現,自定義事件只能分發給在觸發前已訂閱的消息,針對那些先觸發,後訂閱的內容,並不能獲得一個很好的處理,因此,若是要解決這種弊端,就必須加一個離線的事件緩存。除此以外,發佈訂閱也有一些弊端,那就是每次發佈消息,都會觸發全部的事件監聽回調,儘管大多數狀況下並不想觸發全部回調內容,因此在這種狀況下,最好對事件加一些命名空間,以縮小其生效範圍。框架
如下爲支持離線事件代碼,只是對事件加了一個標記:異步
class EventEmitter { constructor() { //回調中心 this.listenerMap = {}; //離線事件列表 this.offlineListenerList = []; } /** * 添加事件監聽 * @param type 事件類型 * @param fn 回調函數 * @param flag 是不是離線事件 */ on(type, fn, flag) { if (!this.listenerMap[type]) { this.listenerMap[type] = []; } this.listenerMap[type].push(fn); //若是註冊了離線事件,則在監聽事件時,須要檢測是否有離線事件緩存 if (flag) { let index = this.offlineListenerList.findIndex(vv => vv.type === type); if (index !== -1) { fn.call(this, this.offlineListenerList[index].params); //清空該條離線事件記錄 this.offlineListenerList.splice(index, 1); } } } /** * 觸發事件監聽 * @param type 事件類型 * @param params 載荷參數 * @param flag */ emit(type, params, flag) { let fnList = this.listenerMap[type]; if (fnList && Array.isArray(fnList)) { fnList.forEach(fn => { fn.apply(this, params); }); } //若是註冊的是離線事件,則吧 if (flag) { this.offlineListenerList.push({ type, params }); } } /** * 移除事件監聽 */ off(type, fn) { if (!this.listenerMap[type]) { return false; } else { let index = this.listenerMap[type].findIndex(vv => vv === fn); if (index === -1) { return false; } else { this.listenerMap[type].splice(index, 1); } } } /** * 只觸發一次 * @param type * @param fn */ once(type, fn) { let fnList = this.listenerMap[type]; let params = Array.from(arguments); if (!fnList || fnList.length === 0) { return false; } else { let index = fnList.findIndex( vv => vv === fn); fnList[index].apply(this, params.slice(1)); fnList.splice(index, 1); } } } let event = new EventEmitter(); event.emit('test', 1, true); event.on('test', params => { console.log('offline', params) }, true); event.on('cc', () => { console.log('normal', 22222); }); event.emit('cc');