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
-Watche
r-组件
是一一对应的,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) |