1、何為觀察者模式?
觀察者模式,又可以稱之為發布-訂閱模式,觀察者,顧名思義,就是一個監聽者,類似監聽器的存在,一旦被觀察/監聽的目標發生的情況,就會被監聽者發現,這么想來目標發生情況到觀察者知道情況,其實是由目標將情況發送到觀察者的。
觀察者模式多用於實現訂閱功能的場景,例如微博的訂閱,當我們訂閱了某個人的微博賬號,當這個人發布了新的消息,就會通知我們。
2、觀察者模式解決的問題?
解決主體對象與觀察者之間功能耦合。
3、實現觀察者步驟:
(1)創建一個觀察者:
把觀察者或者消息系統看作一個對象,那么他應該包含兩個方法,一個是接受消息,一個是向中轉站發送相應消息。
首先,我們把需要的把觀察者對象創建出來,有一個消息容器和三個方法,分別是,訂閱消息的方法,取消訂閱消息的方法,發送訂閱消息的方法。
var Observer = (function(){ //防止消息隊列暴露而被篡改,所以將消息容器作為靜態私有變量保存 var __messsages = {}; return { //注冊信息接口 regist: function(){}, //發布信息的接口 fire: function(){}, //移除信息接口 remove: function(){} } })();
觀察者對象的雛形出來了,我們需要做的事情就是實現這三個方法
(2)實現訂閱方法:
我們首先實現消息注冊方法,注冊方法的作用是將訂閱者注冊的消息推入到消息隊列中,因此我們需要接受兩個參數:消息類型和以及相應的處理動作,在推入到消息隊列時如果此消息不存在應該創建一個該消息類型並將該消息放入到消息隊列中,如果此消息存在則應該將消息執行方法推入該消息對應的執行方法隊列中,這么做目的是保證多個模塊注冊同一個消息能順利執行。
regist: function(type,fn){ //如果此消息不存在,則應該創建一個該消息類型 if(typeof __messages[type] === 'undefined'){ //將對象推入到該消息對應的動作執行隊列中 __messages[type] = [fn]; //如果此消息存在 }else{ //將動作方法推入該消息對應的動作執行序列中 __messages[type].push(fn); } }
(3)實現發布方法:
對於發布消息方法,其功能是當觀察者發布一個消息時將所有訂閱者訂閱的消息一次執行。
故應該接受兩個參數,消息類型以及動作執行時需要傳遞的參數,當然在這里消息類型是必須的。在執行消息隊列之前校驗消息的存在是很有必要的。
然后遍歷消息執行方法隊列,並依此執行。
然后將消息類別以及傳遞的參數打包后依次傳入消息隊列執行方法中。
fire: function(type,args){ //如果該消息沒有被注冊,則返回 if(!__messages[type]){ return ; //定義消息信息 var events = { type:type, args:args||{} }, i=0; len = __messages[type].length; for(;i<len;i++){ //依次執行注冊的消息對應的動作序列 __messages[type][i].call(this,events); } }
(4)實現注銷方法:
最后是消息注銷方法,其功能是將訂閱者注銷的消息從消息隊列清除,因此我們也需要兩個參數,即消息類型以及執行的某一個動作。當然為了避免刪除消息動作時消息不存在情況的出現,對消息隊列中消息的存在性校驗也很有必要的。
remove:function(type,fn){ //如果消息動作隊列存在 if(__messages[type] instanceof Array){ //從最后一個消息動作遍歷 var i = __messages[type].length-1; for(;i>=0;i--){ //如果存在該動作則在消息動作中移除相應動作 __messages[type][i] === fn && __messages[type].splice(i,1); } } }
3、其他實現觀察者方式(使用ES6 Class 方式實現鏈式調用的觀察者模式):
/** * 發布訂閱模式(觀察者模式) * handles: 事件處理函數集合 * on: 訂閱事件 * emit: 發布事件 * off: 刪除事件 **/ class PubSub { constructor() { this.handles = {}; } // 訂閱事件 on(eventType, handle) { if (!this.handles.hasOwnProperty(eventType)) { this.handles[eventType] = []; } if (typeof handle == 'function') { this.handles[eventType].push(handle); } else { throw new Error('缺少回調函數'); } return this; } // 發布事件 emit(eventType, ...args) { if (this.handles.hasOwnProperty(eventType)) { this.handles[eventType].forEach((item, key, arr) => { item.apply(null, args); }) } else { throw new Error(`"${eventType}"事件未注冊`); } return this; } // 刪除事件 off(eventType, handle) { if (!this.handles.hasOwnProperty(eventType)) { throw new Error(`"${eventType}"事件未注冊`); } else if (typeof handle != 'function') { throw new Error('缺少回調函數'); } else { this.handles[eventType].forEach((item, key, arr) => { if (item == handle) { arr.splice(key, 1); } }) } return this; // 實現鏈式操作 } } // 下面做一些騷操作 let callback = function () { console.log('you are so nice'); } let pubsub = new PubSub(); pubsub.on('completed', (...args) => { console.log(args.join(' ')); }).on('completed', callback); pubsub.emit('completed', 'what', 'a', 'fucking day'); pubsub.off('completed', callback); pubsub.emit('completed', 'fucking', 'again');
