有些地方说观察者模式和发布/订阅模式是一样的,其实是不完全等同的,发布/订阅模式中,其解耦能力更近一步,发布者只要做好消息的发布,而不关心消息有没有订阅者订阅。而观察者模式则要求两端同时存在
Vue能够实现当一个数据变更时,视图就进行刷新,而且用到这个数据的其他地方也会同步变更;而且,这个数据必须是在有被依赖的情况下,视图和其他用到数据的地方才会变更。 所以,Vue要能够知道一个数据是否被使用,实现这种机制的技术叫做依赖收集
每个组件都有自己相应的watcher实例,在渲染组件的过程中,会把属性记录为依赖,我们操纵一个数据时,依赖项的setter会被调用,从而通知watcher重新计算,从而致使与之相关联的组件得以更新
所以在getter里,我们进行依赖收集(所谓依赖,就是这个组件所需要依赖到的数据),当依赖的数据被设置时,setter能获得这个通知,从而告诉render()函数进行重新计算。
Vue依赖收集与观察者模式
Vue源码中,实现依赖收集主要有三个类,
Dep: 扮演观察目标(被观察者)的角色,每一个数据都会有Dep实例,它内部有subs队列,保存着依赖本数据的观察者,当本数据发生变化时,调用dep.notify通知所有观察者,watcher:扮演观察者的角色,进行观察者函数的包装
==关于 Observer的解释就是这是一个处理依赖收集中对数组和对象监听的类==,如果value是个对象,就执行walk()过程,遍历对象把每一项数据都变为可观测数据(调用defineReactive方法处理) - 如果value是个数组,就执行observeArray()过程,递归地对数组元素调用observe(),以便能够对元素还是数组的情况进行处理
如何观察数组变化
在处理数组变化时,Vue是先对数组进行一个增强操作,就是在数组的原型链上定义一系列操作(保留原来操作的基础上,植入我们特定的操作代码)
1 | const arrayMethods = Object.create(Array.prototype); |
overrideArrayProto(array){
// 保存原始 Array 原型
var originalProto = Array.prototype,
// 通过 Object.create 方法创建一个对象,该对象的原型就是Array.prototype
overrideProto = Object.create(Array.prototype),
self = this,
result;
// 遍历要重写的数组方法
Object.keys(OAM).forEach(function(key, index, array){
var method = OAM[index],
oldArray = [];
// 使用 Object.defineProperty 给 overrideProto 添加属性,属性的名称是对应的数组函数名,值是函数
Object.defineProperty(overrideProto, method, {
value: function(){
oldArray = this.slice(0);
var arg = [].slice.apply(arguments);
// 调用原始 原型 的数组方法
result = originalProto[method].apply(this, arg);
// 对新的数组进行监测
self.observe(this);
// 执行回调
self.$callback(this, oldArray);
return result;
},
writable: true,
enumerable: false,
configurable: true
});
}, this);
// 最后 让该数组实例的 __proto__ 属性指向 假的原型 overrideProto
array.__proto__ = overrideProto;
}
}
### 先总结一下Data、Observer、Dep和Watcher之间的关系
> Data通过Observer转换成了getter/setter的形式来追踪变化。
当外界通过Watcher读取数据时,会触发getter从而将Watcher添加到依赖中。
当数据发生了变化时,会触发setter,从而向Dep中的依赖(Watcher)发送通知。
Watcher接收到通知后,会向外界发送通知,变化通知到外界后可能会触发视图更新,也有可能触发用户的某个回调函数等。