觀察者模式:定義了對象間一種一對多的依賴關係,當目標對象 Subject 的狀態發生改變時,全部依賴它的對象 Observer 都會獲得通知。
簡單點:女神有男友了,朋友圈曬個圖,甜蜜宣言 「老孃成功脫單,但願大家歡喜」。各位潛藏備胎紛紛失戀,只能安慰本身你不是惟一一個。前端
Subject
,擁有方法:添加 / 刪除 / 通知 Observer
;Observer
,擁有方法:接收 Subject
狀態變動通知並處理;Subject
狀態變動時,通知全部 Observer
。Subject
添加一系列 Observer
, Subject
負責維護與這些 Observer
之間的聯繫,「你對我有興趣,我更新就會通知你」。vue
// 目標者類 class Subject { constructor() { this.observers = []; // 觀察者列表 } // 添加 add(observer) { this.observers.push(observer); } // 刪除 remove(observer) { let idx = this.observers.findIndex(item => item === observer); idx > -1 && this.observers.splice(idx, 1); } // 通知 notify() { for (let observer of this.observers) { observer.update(); } } } // 觀察者類 class Observer { constructor(name) { this.name = name; } // 目標對象更新時觸發的回調 update() { console.log(`目標者通知我更新了,我是:${this.name}`); } } // 實例化目標者 let subject = new Subject(); // 實例化兩個觀察者 let obs1 = new Observer('前端開發者'); let obs2 = new Observer('後端開發者'); // 向目標者添加觀察者 subject.add(obs1); subject.add(obs2); // 目標者通知更新 subject.notify(); // 輸出: // 目標者通知我更新了,我是前端開發者 // 目標者通知我更新了,我是後端開發者
觀察者模式雖然實現了對象間依賴關係的低耦合,但卻不能對事件通知進行細分管控,如 「篩選通知」,「指定主題事件通知」 。git
好比上面的例子,僅通知 「前端開發者」 ?觀察者對象如何只接收本身須要的更新通知?上例中,兩個觀察者接收目標者狀態變動通知後,都執行了 update()
,並沒有區分。github
「00後都在追求個性的時代,我能不能有點不同?」,這就引出咱們的下一個模式。進階版的觀察者模式。「發佈訂閱模式」,部分文章對二者是否同樣都存在爭議。編程
僅表明我的觀點:兩種模式很相似,可是仍是略有不一樣,就是多了個第三者,因 JavaScript 非正規面嚮對象語言,且函數回調編程的特色,使得 「發佈訂閱模式」 在 JavaScript 中代碼實現可等同爲 「觀察模式」。後端
發佈訂閱模式:基於一個事件(主題)通道,但願接收通知的對象 Subscriber 經過自定義事件訂閱主題,被激活事件的對象 Publisher 經過發佈主題事件的方式通知各個訂閱該主題的 Subscriber 對象。
發佈訂閱模式與觀察者模式的不一樣,「第三者」 (事件中心)出現。目標對象並不直接通知觀察者,而是經過事件中心來派發通知。設計模式
// 事件中心 let pubSub = { list: {}, subscribe: function (key, fn) { // 訂閱 if (!this.list[key]) { this.list[key] = []; } this.list[key].push(fn); }, publish: function(key, ...arg) { // 發佈 for(let fn of this.list[key]) { fn.call(this, ...arg); } }, unSubscribe: function (key, fn) { // 取消訂閱 let fnList = this.list[key]; if (!fnList) return false; if (!fn) { // 不傳入指定取消的訂閱方法,則清空全部key下的訂閱 fnList && (fnList.length = 0); } else { fnList.forEach((item, index) => { if (item === fn) { fnList.splice(index, 1); } }) } } } // 訂閱 pubSub.subscribe('onwork', time => { console.log(`上班了:${time}`); }) pubSub.subscribe('offwork', time => { console.log(`下班了:${time}`); }) pubSub.subscribe('launch', time => { console.log(`吃飯了:${time}`); }) // 發佈 pubSub.publish('offwork', '18:00:00'); pubSub.publish('launch', '12:00:00'); // 取消訂閱 pubSub.unSubscribe('onwork');
發佈訂閱模式中,訂閱者各自實現不一樣的邏輯,且只接收本身對應的事件通知。實現你想要的 「不同」。api
DOM 事件監聽也是 「發佈訂閱模式」 的應用:函數
let loginBtn = document.getElementById('#loginBtn'); // 監聽回調函數(指定事件) function notifyClick() { console.log('我被點擊了'); } // 添加事件監聽 loginBtn.addEventListener('click', notifyClick); // 觸發點擊, 事件中心派發指定事件 loginBtn.click(); // 取消事件監聽 loginBtn.removeEventListener('click', notifyClick);
發佈訂閱的通知順序:post
on
和 trigger
,$.callback()
;$on/$emit
jQuery 的 $.Callback() 更像是觀察者模式的應用,不能更細粒度管控。
function notifyHim(value) { console.log('He say ' + value); } function notifyHer(value) { console.log('She say ' + value); } $cb = $.Callbacks(); // 聲明一個回調容器:訂閱列表 $cb.add(notifyHim); // 向回調列表添加回調:訂閱 $cb.add(notifyHer); // 向回調列表添加回調:訂閱 $cb.fire('help'); // 調用全部回調: 發佈
利用 Object.defineProperty()
對數據進行劫持,設置一個監聽器 Observer
,用來監聽數據對象的屬性,若是屬性上發生變化了,交由 Dep
通知訂閱者 Watcher
去更新數據,最後指令解析器 Compile
解析對應的指令,進而會執行對應的更新函數,從而更新視圖,實現了雙向綁定。
Observer
(數據劫持)Dep
(發佈訂閱)Watcher
(數據監聽)Compile
(模版編譯)關於 Vue 雙向數據綁定原理,可自行參考其它文章,或推薦本篇 《 vue雙向數據綁定原理》。
Vue
中,父組件經過 props
向子組件傳遞數據(自上而下的單向數據流)。父子組件之間的通訊,經過自定義事件即 $on
, $emit
來實現(子組件 $emit
,父組件 $on
)。
原理其實就是 $emit
發佈更新通知,而 $on
訂閱接收通知。Vue
中還實現了 $once
(一次監聽),$off
(取消訂閱)。
// 訂閱 vm.$on('test', function (msg) { console.log(msg) }) // 發佈 vm.$emit('test', 'hi')
都是定義一個一對多的依賴關係,有關狀態發生變動時執行相應的通知。
發佈訂閱模式更靈活,是進階版的觀察者模式,指定對應分發。
Object.defineProperty()
屬性描述符;Proxy
代理;參考文章:
本文首發Github,期待Star!
https://github.com/ZengLingYong/blog
做者:以樂之名 本文原創,有不當的地方歡迎指出。轉載請指明出處。