1.觀察者模式是只有兩個對象:目標對象(類)去直接作用觀察者(類)去更新,這個更新是在觀察者內部調用自身的update方法去執行響應或者說去做更新。
耦合度較高,因為觀察者是在目標對象的“體內”去執行的。目標對象在自己體內去添加觀察者列表,然后調用自身的發布事件觸發觀察者調用自己的update方法執行。
按照上面的思路寫一個簡易版的觀察者模式:
1 // 觀察者模式:
2 class Observer { 3 constructor(cb) { 4 // 判斷傳進來的是不是一個函數,如果是就掛載到this上
5 if (Object.prototype.toString.call(cb) !== "[object Function]") { 6 console.log('不是一個函數'); 7 } else { 8 this.cb = cb; 9 } 10 } 11 // 執行更新
12 update() { 13 this.cb(); 14 } 15 } 16 // 目標對象
17 class Target { 18 constructor() { 19 // 存儲觀察者
20 this.observerList = []; 21 }; 22 // 添加觀察者
23 add(observer) { 24 this.observerList.push(observer) 25 }; 26 // 通知所有觀察者觸發更新
27 notify() { 28 for (let i = 0; i < this.observerList.length; i++) { 29 this.observerList[i].update(); 30 } 31 } 32 } 33
34 let fn1 = function() {console.log('我要觸發了');} 35 let fn2 = function() {console.log('我要觸發了2');} 36 let o1 = new Observer(fn1); 37 let o2 = new Observer(fn2); 38
39 let t = new Target(); 40 // 把o1和o2兩個觀察者存入觀察者數組
41 t.add(o1); 42 t.add(o2); 43 // 觸發依次調用所有觀察者的update方法,像不像addEventLister?
44 // 比如給一個按鈕綁定點擊事件,對應不同的回調函數,這里的fn1和fn2就相當於addEventListener中的回調函數
45 t.notify();
輸出:

vue響應式觀察者模式部分:

調用: Dep.target就是watcher實例,相當於new Watcher的行為,把實例添加進入了目標對象中,目標對象中的notify就執行了。

2.發布訂閱模式
簡單版本自寫:
版本1:模仿vue響應式的發布訂閱
1 // 收集依賴-相當於vue的Observer中definedPrototype中的get
2 class Observer { 3 constructor() { 4 this.saveArr = []; 5 } 6 add(fn) { 7 this.saveArr.push(fn); 8 return this.saveArr; 9 } 10 } 11 // 調度中心- 相當於Vue的Dep
12 class Dep { 13 constructor() { 14 this.getArr = []; 15 } 16 on(event) { 17 let ob = new Observer(); 18 this.getArr = ob.add(event); 19 }; 20 emit(event) { 21 let notify = new Notify(); 22 notify.update(event); 23 } 24 } 25 // 發布者 - 相當於vue的Observer中definedPrototype中的set
26 class Notify { 27 constructor() { 28 this.arr = []; 29 } 30 update(fn) { 31 this.arr.push(fn); 32 for (var i = 0; i < this.arr.length; i++) { 33 this.arr[i](); 34 } 35 } 36 } 37
38 var fn1 = function() { 39 console.log('1'); 40 } 41 var fn2 = function() { 42 console.log('2'); 43 } 44 var d1 = new Dep(); 45 var d2 = new Dep(); 46
47 d1.on(fn1); 48 d2.on(fn2); 49 d1.emit(fn1); 50 d2.emit(fn2); 51 // 輸入1,2
版本2:一個class類,類本身是一個調度中心,里面的on作為訂閱者,emit是發布者
1 var f1 = function() { 2 console.log('f1'); 3 } 4 var f2 = function() { 5 console.log('f2'); 6 } 7 // EventEmitter這個類本就是一個調度中心
8 class EventEmitter { 9 constructor() { 10 this.getArr = {}; 11 } 12 // 訂閱
13 on(type, event) { 14 if (type) { 15 this.getArr[type] = []; 16 this.getArr[type].push(event); 17 } 18 }; 19 // 發布
20 emit(type, event) { 21 for (var i = 0; i < this.getArr[type].length; i++) { 22 this.getArr[type][i](); 23 } 24 } 25 } 26
27 var ee = new EventEmitter(); 28 ee.on("test1", f1); 29 ee.on("test2", f2); 30 ee.emit("test1", f1); 31 ee.emit("test2", f2); 32 // 輸出: f1,f2

3.vue中的數據響應式其實是個發布訂閱模式:
數據劫持中,get函數是觸發監聽,把data都綁定上了watcher
set函數是發布者,因為只要數據已更新,就會觸發set函數,set函數就會告訴Dep讓其執行notify,相當於this.$emit('notify'),set函數扮演的就是甲方,是個發號施令的角色。
Dep是調度中心,負責收集依賴和收到發布者的命令執行notify方法,調度中心通知Wather執行更新方法。watcher去調用自己的update方法,在compile編譯模板的時候,new Watcher的時候,拿到了Watcher的回調函數(得到set修改后最新值)然后去通過textContent通過打補丁的方式,經歷Diff算法,最終更新DOM。
get函數是訂閱者,訂閱wather
簡單來說就是:
調度者作為訂閱者和發布者的紐帶,就像一個房產中介一樣,租戶就好比訂閱者,去關注中介的發的租房信息,房東就好比發布者,去告訴中介他房子要出租了。
中介(Dep)就收集了很多租房者的微信(watcher),如果房東有房子告訴中介你去發微信(這個行為就是發布者知道調度者的方式notify)告訴租戶們(set方法),就會立馬通過微信通知(notify)租房者(watcher),租房者會去跟媳婦商量商量做出響應(update更新:跟虛擬DOM切磋,diff算法,patch打補丁)
上面說的不咋對,新的理解:
訂閱階段:
在最開始初始化vue階段,走到模板編譯階段,也就是compile,加載compile.js的時候,回去new Watcher()實例,那么就會觸發watcher中執行 把watcher實例給到 數據劫持的get方法(訂閱者),給每一個data都加上了wather,這些watcher又被push進去了調度中心Dep。
發布階段:當data數據發生改變后,就會觸發set方法(發布者),set方法中就會讓調度中心Dep去讓他里面的watcher執行他們的update方法,watcher中的update方法呢,又會調用Watcher類中傳入的回調函數,這個回調函數執行,並且傳入set改了那個數據的最新值,這樣complie.js中 去new Watcher實例的時候(數據每次發生變化的時候,就會觸發beforeUpdate和upDate鈎子進行重新編譯,自然就會觸發complie.js),傳入的回調函數就可以收到Wather實例中執行的回調函數的值。拿到值后,去更新DOM。
截幾個關鍵點:
complie.js中:頁面初始化,new Watcher,觸發Wather中的方法(觸發數據劫持的get方法,添加監聽器)
等到data數據發生變化,要重新編譯模板,又會觸發compile.js,自然又觸發了Wather,拿到watcher的回調參數,更新DOM

數據劫持:

Dep:

Wather:

小小參考:
