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:
小小参考: