Vue源码01 Vue响应式原理
Vue响应式原理
响应式过程

简单来说,依赖收集的过程是:
- 在组件
init的过程中,为data中的属性添加getter/setter方法 - 在组件渲染过程中(
render函数执行时),每个组件实例内部会实例化一个Watcher对象,data中的属性会被touch,触发getter方法,记录组件和属性的对应关系 - 当属性更新时,访问
setter方法,会调用对应的wachter重新计算,调用render函数,导致关联组件更新
data的getter/setter
在init阶段,data中的属性会被添加getter和setter方法,手段就是调用Object.defineProperty方法
1 | function defineReactive(obj: Object, key: string, ...) { |
data中的每个属性都有一个dep对象,getter中会调用dep.depend()方法
Watcher的创建
Vue对象在init之后进入mount阶段,关键函数是mountComponent:
1 | mountComponent(vm: Component, el: ? Element, ...) { |
在组件渲染过程中,实例化了一个Watcher对象,它有两个参数,第一个参数vm就是当前组件实例,第二个参数updateComponent会调用vm._render和vm._update方法,主要目的就是更新页面
vm._render目的是将Vue对象渲染为虚拟DOMvm._update目的是将虚拟DOM创建或更新为真实DOM
在组件需要更新的时候,Watcher就会被调用,更新页面
收集依赖
1 | class Watcher { |
在Watcher的构造函数中,会调用expOrFn方法,这个expOrFn就是上面的updateComponent方法,进而调用vm._render方法
在这个过程中,会访问data中的属性的getter(也就是touch的过程),这是会调用上面提到的dep.depend()方法
为了弄清dep.depend()方法究竟做了什么,在dep对象的构造函数中看:
1 | class Dep { |
dep.depend()实际上调用了Dep.target.addDep(this),而在Watcher的构造函数中有这样的代码Dep.target = this,所以Dep.target.addDep(this)也就是调用了Watcher的addDep方法
而Watcher中的addDep方法简化后:
1 | class Watcher { |
Watcher将这个Dep保存了下来,然后调用了Dep的addSub方法,将Watcher存了进去
我这样理解,Dep记录了所有依赖这个属性的组件和组件的Watcher实例,同样,在Watcher中也记录了当前组件实例都使用了哪些属性
经过这些步骤,Dep.depend的导致addSub方法被调用,将当前的Watcher记录到了组件的Dep的subs中
派发更新
修改data中已经双向绑定后的属性,会调用setter方法,调用了dep.notify方法
1 | class Dep { |
dep.notify方法调用所有依赖这个属性的组件的Watcher实例,运行render方法,更新页面

render-Watcher-组件是一一对应的,data中的每个属性都有对应的dep实例

更详细的细节:

问题
由于在Vue的init过程中,对data中的属性执行了getter和setter转化过程,如果是未初始化话添加的data根属性,则无法被追踪:
1 | var vm = new Vue({ |
还有一个问题:
1 | var vm = new Vue({ |
items是数组,数组的索引并不是属性,很难用Dep去绑定,长度length也没有被处理,解决方法:
1 | vm.items.splice(indexOfItem, 1, newValue) |