user-watcher
在页面中使用的watcher
,即用户定义的watcher
,用于观察一个属性的更新,支持数组定义多个,对象定义单个的形式,在initWatcher
中进行watcher
的初始化之后,在渲染函数进行数据的读取,触发依赖收集时会将user-watcher
的依赖收集进去,data
属性set
更新时会被触发user-watcher
所定义的回调函数(将新旧值传入),支持异步操作
可选项:immediate
deep
sync
immediate
为true时会将实例作为参数传入立即执行回调函数deep
对对象、数组进行深度的依赖收集sync
不把更新watcher放到nextTick队列 而是立即执行更新
render-watcher
每一个组件都有一个render-watcher
当 data/computed
中的有被依赖的属性改变的时候会触发render-watcher
的更新,表达式为
function(){
vm._update(vm.render(), hydrating)
}
- 使用:在组件嵌套中,
props
触发的关联更新等,如父组件会将子组件的watcher
收集用于视图更新的触发 - 初始化:渲染函数的
watcher
,mounted
之后,实例化初始化染函数观察者并触发对render-wather
的收集,渲染函数也是一个对象,初始化过程,生成render-tree
,获取结果时收集render-watcher
依赖,render-tree
更新也就是需要重新渲染视图 - 防止重新收集:
this.depIds.has(dep.id)
来避免重复依赖收集,因在渲染函数中,一个属性被引用多次是常见的 - 更新:
data
更新触发依赖,同时会触发user-watcher
对回调函数完成新旧数据的派发,最后触发render-watcher
更新视图,所有的watcher
默认都是在nextTick
队列中进行异步更新的,当所有的突变完成之后,一次性的执行队列中所有观察者的更新方法,同时清空队列,这样 多次更新同一个watcher
只会更新一次,而频繁的更新可以整合为一次
computed-watcher
- 使用:在
initState
阶段被初始化,具有缓存性质的惰性求值观察者,只有存在依赖性数据并且该数据更新了,computed
值才会更新,否则就取缓存的值,依赖数据是data
中的数据
初始化:computed
计算属性在mounted
之后生成,实例化computed-watcher
初始化computed dirty
属性为true
,并不调用get
方法读取属性 - 计算:当渲染函数访问到
computed
属性,进行求值,并将dirty
设置为false
标识计算过,同时对依赖的属性读取时将computed-watcher
当做依赖收集,并且订阅render-watcher
,所以在内部数据触发set
时会先计算后对比新旧值,有更新则重新渲染视图 - 更新:内部依赖被触发后,设置
dirty
为true
,通过判断this.dep.subs.length
有没有订阅者,如果有则进行取值的计算(计算后将dirty
置为false
),否则等待该值被引用才进行计算
什么时候触发了依赖的收集
准备:在
created
和beforeMounted
之间生成了AST
和render
可执行函数,用到了compile
的createCompiler
生成对象结构的AST语法树
,再通过generator
调用了之前初始化的方法处理各种vue
内置的方法&指令,如v-for v-if
等,通过createElement
、createTextVnode
等生成虚拟DOM树,作为updateComponent
函数返回渲染:在
beforeMounted
和mounted
之间通过将执行这个函数,这个函数会将虚拟DOM
渲染成真正的DOM
,内含有patch
的布丁算法,在执行过程中会触发渲染函数,这时会触发数据属性的get
函数,而这个过程的观察者就是渲染函数,在渲染过程中添加数据的订阅者,watcher
订阅者是Observer
和Compiler
之间的桥梁,将在自身实例化的时候将自己添加到dep
当中,watcher
自身有一个depend
和update
方法,待属性变动dep.notice
通知调用自身的update
方法触发Compile
中绑定的回调
总结
这三种 watcher
执行顺序为 computed watcher
=> user watcher
=> render watcher
,这样做 尽可能的保证了视图更新时数据是最新的
收集依赖是在render tree
渲染时读取render tree
的结构,触发依赖的收集,每一次都只会对一个观察者进行操作,所以一个时间点只有一个Dep.tatget
,说明这个观察者是依赖于当前的数据,就会把这个观察者添加到该数据的subs
里面,会通过id
来防止重复添加,同时会将依赖添加到自身的deps
中以便通过set
调用 dep.notify()