Vue 的 三种 watcher

Souleigh ✨ 等级 162 2 1

user-watcher

在页面中使用的watcher,即用户定义的watcher,用于观察一个属性的更新,支持数组定义多个,对象定义单个的形式,在initWatcher中进行watcher的初始化之后,在渲染函数进行数据的读取,触发依赖收集时会将user-watcher的依赖收集进去,data属性set更新时会被触发user-watcher所定义的回调函数(将新旧值传入),支持异步操作
可选项:immediate deep sync

  • immediate 为true时会将实例作为参数传入立即执行回调函数
  • deep 对对象、数组进行深度的依赖收集
  • sync 不把更新watcher放到nextTick队列 而是立即执行更新

render-watcher

每一个组件都有一个render-watcherdata/computed 中的有被依赖的属性改变的时候会触发render-watcher的更新,表达式为

  function(){
     vm._update(vm.render(), hydrating)
  }
  • 使用:在组件嵌套中,props触发的关联更新等,如父组件会将子组件的watcher收集用于视图更新的触发
  • 初始化:渲染函数的watchermounted之后,实例化初始化染函数观察者并触发对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时会先计算后对比新旧值,有更新则重新渲染视图
  • 更新:内部依赖被触发后,设置dirtytrue,通过判断this.dep.subs.length 有没有订阅者,如果有则进行取值的计算(计算后将dirty置为false),否则等待该值被引用才进行计算

什么时候触发了依赖的收集

  • 准备:在createdbeforeMounted之间生成了ASTrender可执行函数,用到了compilecreateCompiler生成对象结构的AST语法树,再通过generator调用了之前初始化的方法处理各种vue内置的方法&指令,如v-for v-if等,通过createElementcreateTextVnode等生成虚拟DOM树,作为updateComponent函数返回

  • 渲染:在beforeMountedmounted之间通过将执行这个函数,这个函数会将虚拟DOM渲染成真正的DOM,内含有patch的布丁算法,在执行过程中会触发渲染函数,这时会触发数据属性的get函数,而这个过程的观察者就是渲染函数,在渲染过程中添加数据的订阅者,watcher订阅者是ObserverCompiler之间的桥梁,将在自身实例化的时候将自己添加到dep当中,watcher自身有一个dependupdate方法,待属性变动dep.notice通知调用自身的update方法触发Compile中绑定的回调

总结

这三种 watcher 执行顺序为 computed watcher => user watcher => render watcher,这样做 尽可能的保证了视图更新时数据是最新的
收集依赖是在render tree渲染时读取render tree的结构,触发依赖的收集,每一次都只会对一个观察者进行操作,所以一个时间点只有一个Dep.tatget,说明这个观察者是依赖于当前的数据,就会把这个观察者添加到该数据的subs里面,会通过id来防止重复添加,同时会将依赖添加到自身的deps中以便通过set调用 dep.notify()

收藏
评论区