觀察者模式,是JavaScript設計模式之一。固然也不只僅限於JavaScript這門語言,網上對該模式的介紹已經是多如牛毛,並且講得各有特點各有心得。即使如此,筆者仍精心準備了這篇博客,指望用最簡單的方式來介紹下該模式。前端
首先來看下維基百科對 觀察者模式 的解釋:git
觀察者模式是軟件設計模式的一種。在此種模式中,一個目標對象管理全部相依於它的觀察者對象,而且在它自己的狀態改變時主動發出通知。這一般透過呼叫各觀察者所提供的方法來實現。此種模式一般被用來實時事件處理系統。
其實筆者更傾向於它的另外一個名字發佈/訂閱模式(Publish/Subscribe),由於更能表達出該模式的核心思路,那就是:發佈和訂閱兩個過程。是否是還感受模棱兩可?不用擔憂,下面就用我們身邊發生的事情來作個形象化的解釋:
你們都有訂閱網站郵件的經歷吧?若是你沒有的話,emmmmm....那就繼續往下看吧哈哈!!
假如我今天想訂閱xxx公司的郵件,那麼這裏就涉及到兩個對象:我
和xxx公司
,
從行爲上來看就是我訂閱
了xxx公司郵件,xxx公司會發送
郵件給個人郵箱。但某天我不想再收到xxx公司的郵件了,那麼我能夠取消訂閱
,這樣xxx公司就不會再發郵件到個人郵箱。設計模式
說到這裏,是否是就有點眉頭了呢?好,咱們繼續往下說,
經過剛剛的形象化解釋,咱們能夠羅列下觀察者模式的一些核心的東西:數組
對象:我(訂閱者)
, xxx公司(發佈者)
, 能夠直接對應 發佈/訂閱模式(Publish/Subscribe)
行爲:訂閱
、發送
和取消訂閱
說不如作,下面開始用代碼來更直觀的描繪下觀察者模式吧。
首先咱們定義一個發佈者 (至關於xxx公司)函數
let publisher = { }
那麼一塊兒來按照訂閱郵件的過程想象下,發佈者具備那些屬性或者方法?網站
publisher
中一定會有一個方法提供給咱們實現訂閱
。publisher
中一定會有一個方法提供給咱們實現發送
或者說說發佈
。publisher
中一定會有一個方法提供給咱們實現取消訂閱
。publisher
中一定會有一個「註冊表」
來存儲訂閱的對象,也就是說咱們的 郵箱地址。說到這裏一切都瞭然了,下面仍是講想象到的東西用代碼表達出來吧this
let publisher = { registration: {}, subscribe: function (type, fn) {}, unSubscribe: function (type, fnName) {}, publish: function (type, message) {} }
簡單解釋下,spa
registration
就是上面提到的註冊表
,至於爲何把它設計成一個對象是由於考慮到xxx公司可能有更多類型的郵件,好比 遊戲,金融,投資理財等等,因此就把它設計成對象以key-value的形式存儲訂閱者, 好比:{'game':[],'monetary':[]}
該形式subscribe
則是publisher
提供給咱們的對其進行訂閱的方法,參數是type
和 fn
。type就是郵件的類型,fn就是咱們提供給publisher用於通知個人渠道 (郵箱)。在JavaScript中更多的是回調函數。unSubscribe
是publisher
提供給咱們的對其進行取消訂閱的方法,參數是type
和 fnName
。type就很少說了,fnName則是咱們提供給publisher
用於取消訂閱的標誌,好比說郵箱,或者是回調函數的名字等等。publish
說到比較重要的方法,這就是publisher
向全部訂閱者發佈消息的方法。下面開始一步一步得實現三個方法,registration
保持不變:設計
首先是
subscribe
subscribe: function (type, fn) { if (Object.keys(this.registration).indexOf(type) >= 0) { this.registration[type].push(fn); } else { this.registration[type] = []; this.registration[type].push(fn); } }
這裏的思路是將 Callback Function 存儲到registration
對於類型的數組中,以待publish
調用。code
而後是
unSubscribe
unSubscribe: function (type, fnName) { if (Object.keys(this.registration).indexOf(type) >= 0) { let index = -1; this.registration[type].forEach(function (func, idx) { if (func.name === fnName) { index = idx; } }) index > -1 ? this.registration[type].splice(index, 1) : null } }
思路是首先經過 type 肯定數組對象,而後經過方法對象的名字進行判斷,最後直接剔除操做。** 這裏有個小知識點提一下:函數對象的name屬性就是該函數名 **
最後是
publish
publish: function (type, message) { if (Object.keys(this.registration).indexOf(type) >= 0) { for (let fn of this.registration[type]) { fn(message) } } }
思路是經過 type 找到指定數組,而後對數組中的回調函數進行依次調用,達到發佈
的目的。
寫到這裏,發佈者Publisher
已經完成。那麼下面開始寫訂閱者Subscriber
,如上面所說其實訂閱者就是一個 回調函數,例如:
let subscriber = function (param) { //do something }
因此下面將整個代碼展現並演示下效果:
let publisher = { registration: {}, subscribe: function (type, fn) { if (Object.keys(this.registration).indexOf(type) >= 0) { this.registration[type].push(fn); } else { this.registration[type] = []; this.registration[type].push(fn); } }, unSubscribe: function (type, fnName) { if (Object.keys(this.registration).indexOf(type) >= 0) { let index = -1; this.registration[type].forEach(function (func, idx) { if (func.name === fnName) { index = idx; } }) index > -1 ? this.registration[type].splice(index, 1) : null } }, publish: function (type, message) { if (Object.keys(this.registration).indexOf(type) >= 0) { for (let fn of this.registration[type]) { fn(message) } } } } let subscriberA = function (message) { console.log(`A收到通知:${message}`) }; let subscriberB = function (message) { console.log(`B收到通知:${message}`) }; let subscriberC = function (message) { console.log(`C收到通知:${message}`) }; publisher.subscribe('game', subscriberA); publisher.subscribe('game', subscriberB); publisher.subscribe('game', subscriberC); publisher.publish('game', '恭喜RNG得到LOL 2018季中賽冠軍!')
運行看下結果:
結果如想象中同樣。
那再試一下取消訂閱
,在 publish 以前加一段
publisher.unSubscribe('game', subscriberB.name)
再運行看下結果:
咱們已經看到 訂閱者B 在取消訂閱後就沒再收到任何消息。
其實觀察者模式能作的東西還有不少,好比事件的監聽、狀態發生變化時的廣播等等。已經有過接觸的朋友均可能意識到這個模式特別靈活,在兩個角色之間正常通訊的同時也儘量得實現瞭解耦,給開發帶來極大的便利。其中有名的 Knockout 的核心之一就是觀察者模式,因此說觀察者模式在前端開發中起到了舉足輕重的做用。
源碼 在這,有興趣的朋友能夠看下
好了,寫到這裏本篇博客就結束了。有問題的朋友能夠在下方討論;若是文章有不足或者錯誤的地方,煩請你們多多指正。Thanks !!!