觀察者模式又叫作發佈-訂閱模式。這是一種一對多的對象依賴關係,當被依賴的對象的狀態發生改變時,全部依賴於它的對象都將獲得通知。git
就如咱們在專賣店預約商品(如:蘋果手機),咱們會向專賣店提交預約申請,而後店家受申請,正常這樣就完事了。假如,近段時間蘋果手機的需求很大,而商品有限,那麼商家就會要這些果粉預留電話等待通知,等到手機一到,商家就會遍歷果粉預留信息,而後發通知給這些果粉。生活中商家強調客戶在家等通知便可,而且說一有消息就會通知客戶,而不會傻到要客戶主動打電話詢問,這樣不只客戶的代價比較大,商家的負荷更大,用戶的輪詢方式也從打電話變成了查看短信息。github
發佈和訂閱這兩個對象是鬆耦合地聯繫在一塊兒的,它們不用彼此熟悉內部的實現細節,但這不影響它們之間的通訊,它們只要知道彼此須要作什麼就行。當有新訂閱者增長時,發佈者不須要任何更改,一樣的當發佈者改變時,訂閱者也不會受到影響。設計模式
就像新聞聯播同樣裏面的央視主持人換了,也不影響咱們看央視的新聞聯播,一樣你看或不看新聞聯播,對央視來講也無影響。數組
在異步通訊中觀察者模式也是大有好處,發佈者只需按順序的發佈事件便可,而訂閱者只需在異步運行期間訂閱相關事件便可。緩存
在JavaScript中觀察者模式的實現主要用事件模型。服務器
document.body.addEventListener('click', function() { console.log('hello world!'); });
相信這樣的代碼很多的同窗都寫過,但我要說這其實就是一種觀察者模式的實現,可能一些童鞋還不信,那麼看一看修改後的代碼。app
// 發佈者 var pub = function() { console.log('歡迎訂閱!') } // 訂閱者 var sub = document.body; // 訂閱者實現訂閱 sub.addEventListener('click', pub, false);
訂閱者能夠任意的添加,發佈者也能夠隨意的修改。dom
雖然,使用dom事件能夠輕鬆解決咱們開發中的一部分問題;可是還有一些問題須要咱們使用自定義事件來完成。
那面就說一說如何用自定義事件實現代理。異步
咱們還以預約手機爲例,參考dom事件的原理來實現觀察者模式,用用戶的電話號碼做爲類型,用戶的定購信息用一個回調函數來表示。函數
基本概念定義以下:
注:緩存列表,我將它定義爲一個對象,用戶的電話號碼做爲key,用戶的預約信息是個數組做爲value。
代碼實現以下:
// 定義商家 var merchants = {}; // 定義預約列表 merchants.orderList = {}; // 將增長的預訂者添加到預約客戶列表中 merchants.listen = function(id, info) { if(!this.orderList[id]) { this.orderList[id] = []; } this.orderList[id].push(info); console.log('預約成功') }; //發佈消息 merchants.publish = function() { var id = Array.prototype.shift.call(arguments); var infos = this.orderList[id]; // 判斷是否有預訂信息 if(!infos || infos.length === 0) { console.log('您尚未預訂信息!'); return false; } // 若是有預訂信息,則循環打印 for (var i = 0, info; info = infos[i++];) { console.log('尊敬的客戶:'); info.apply(this, arguments); console.log('已經到貨了'); } }; // 定義一個預訂者customerA,並指定預約信息 var customerA = function() { console.log('黑色至尊版一臺'); }; // customerA 預約手機,並留下預定電話 merchants.listen('15888888888', customerA); // 預約成功 // 商家發佈通知信息 merchants.publish('15888888888'); /** 尊敬的客戶: 黑色至尊版一臺 已經到貨了 */
固然,現實中咱們能夠預約,那麼也能夠取消預約。其實取消預約的方式也比較簡單,就是將客戶從預約列表中清除出去。代碼實現以下:
merchants.remove = function(id, fn) { var infos = this.orderList[id]; if(!infos) return false; if(!fn) { infos && (infos.length = 0); } else { for(var i = 0, len = infos.length; i < len; i++) { if(infos[i] === fn) { infos.splice(i, 1); } } } }; merchants.remove('15888888888', customerA); merchants.publish('15888888888'); // 您尚未預訂信息!
實現的代碼結構以下:
var observer = (function() { var orderList = {}, listen, publish, remove; listen = function(id, fn) { ... }; publish = function() { ... }; remove = function(id, fn) { ... }; return { listen: listen, publish: publish, remove: remove } })();
優勢:
使用了全局的觀察者模式後,咱們不用管商家是誰,只要他能提供咱們所須要的東西便可;並且咱們也避免了爲不一樣的商家都建立listen,publish,remove方法,這樣能夠減小資源的浪費。
缺點:
使用全局的觀察者模式會明顯下降對象之間的聯繫。一些方法將會被隱藏,而有時咱們偏偏須要這些方法的暴露。
在我被問到這個問題時,我也是一愣,當時腦殼裏就冒出了‘你怎麼不問是先有雞,仍是先有蛋’這樣的想法。
按照個人理解咱們實現觀察者模式,都是訂閱者先訂閱,而後接收發布者的通知消息。沒有反過來想,發佈者先發布一條消息,而後等訂閱者接收,由於在個人想象中,若是沒有訂閱者,這消息怎麼成功發佈。
後來有人跟我說有這樣的業務實現,當時我就不假思索的問什麼業務,他說QQ的離線模式。這種先發布後訂閱的形式是將信息先存儲起來,等到訂閱者訂閱,就當即將信息發送給訂閱者。如:當咱們將QQ調到離線模式,咱們就沒法接收信息;當咱們將QQ調到登陸模式,就立刻收在離線模式期間接收到的信息。
這樣的例子在生活中也有不少,還拿天氣預報,它也能夠理解爲是先發布,咱們後訂閱的模式。天預報信息會發布在網上,存儲在各個服務器上,咱們須要時打開手機就能夠獲得。
注:提到觀察者模式咱們就不得不說一下推模型和拉模型。推模型在事件發生時,發佈者會將變化狀態和數據都推送給訂閱者;拉模型在事件發生時,發佈者只會給訂閱者一個狀態改變通知,訂閱者會根據發佈者提供的接口主動拉取數據。