响应式系统原理

有些地方说观察者模式和发布/订阅模式是一样的,其实是不完全等同的,发布/订阅模式中,其解耦能力更近一步,发布者只要做好消息的发布,而不关心消息有没有订阅者订阅。而观察者模式则要求两端同时存在

Vue能够实现当一个数据变更时,视图就进行刷新,而且用到这个数据的其他地方也会同步变更;而且,这个数据必须是在有被依赖的情况下,视图和其他用到数据的地方才会变更。 所以,Vue要能够知道一个数据是否被使用,实现这种机制的技术叫做依赖收集
image
每个组件都有自己相应的watcher实例,在渲染组件的过程中,会把属性记录为依赖,我们操纵一个数据时,依赖项的setter会被调用,从而通知watcher重新计算,从而致使与之相关联的组件得以更新
所以在getter里,我们进行依赖收集(所谓依赖,就是这个组件所需要依赖到的数据),当依赖的数据被设置时,setter能获得这个通知,从而告诉render()函数进行重新计算。

Vue依赖收集与观察者模式

Vue源码中,实现依赖收集主要有三个类,
Dep: 扮演观察目标(被观察者)的角色,每一个数据都会有Dep实例,它内部有subs队列,保存着依赖本数据的观察者,当本数据发生变化时,调用dep.notify通知所有观察者,watcher:扮演观察者的角色,进行观察者函数的包装
==关于 Observer的解释就是这是一个处理依赖收集中对数组和对象监听的类==,如果value是个对象,就执行walk()过程,遍历对象把每一项数据都变为可观测数据(调用defineReactive方法处理) - 如果value是个数组,就执行observeArray()过程,递归地对数组元素调用observe(),以便能够对元素还是数组的情况进行处理

如何观察数组变化

在处理数组变化时,Vue是先对数组进行一个增强操作,就是在数组的原型链上定义一系列操作(保留原来操作的基础上,植入我们特定的操作代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const arrayMethods = Object.create(Array.prototype); 
// 形成:arrayMethods.__proto__ -> Array.prototype
const originalPush = arrayMethods.push;
Object.defineProperty(arrayMethods, 'push', {
configurable: true,
enumerable: false,
writable: true,
value(...args) {
const result = originalPush.apply(this, args);
console.log('对数组进行了push操作,加入了值:', args);
return result;
}
})
data.arr.__proto__ = arrayMethods
data.arr.push([5, 6], 7) // 对数组进行了push操作,加入了值:[5, 6], 7
```
所以,只要对每一个数组操作方法进行这么一个处理,那么我们也有办法在数组变更时,通知观察者了,Vue的具体实现如下

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接收到通知后,会向外界发送通知,变化通知到外界后可能会触发视图更新,也有可能触发用户的某个回调函数等。
上一篇

探索浏览器渲染器原理