Skip to content

watcher

经常会提到computedwatch的原理是什么,因此这里单独说明 Vue源码中 watcher 相关内容。在源码中,分为三类 watcher

  • 渲染 watcher
  • watch 用户watcher
  • computed 计算属性 下面将详细说明。

渲染 watcher

在初始化部分已经说明过渲染watcher,它在mountComponent中初始化。

js
export function mountComponent(){
  ...
   new Watcher(vm, updateComponent, noop, {
    before() {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  ...

}

用户watcher之 watch

它是在initstate中初始化的。它的原理就是对watch的每个属性创建一个watcher对象;watcher 在初始化时会将监听的目标值缓存到 watcher.value 中, 因此触发 data[key]get 方法, 被对应的 dep 进行依赖收集; 当 data[key] 发生变动时触发 set 方法, 执行 dep.notify 方法, 通知所有收集的依赖 watcher , 触发收集的 watcher , 在run中执行 watcher.cb , 也就是 watch 中的监听函数。

js
// this.lazy为true 说明是计算属性
this.value = this.lazy
      ? undefined
      : this.get()

计算属性computed

computed 也是在initState中初始化的,不过和watch有所不同,从步骤来看如下:

  1. 对每个计算属性创建一个watcher对象 core/observer/watcher.js
js
// this.lazy为true 说明是计算属性
this.value = this.lazy
      ? undefined
      : this.get()
  1. 初始化computed的时候 src/core/instance/state.js
js
function defineComputed(target, key) {
 ...
 Object.defineProperty(target, key, {
   enumerable: true,
   configurable: true,
   get: createComputedGetter(key),
   set: noop
 })
}

这个方法的作用是让computed成为一个响应式数据,并定义它的get属性,当页面执行渲染访问的computed时(也就是render阶段),才会触发get然后执行createComputedGetter

  1. createComputedGetter
js
function createComputedGetter (key) { // 高阶函数
 return function () {  // 返回函数
   const watcher = this._computedWatchers && this._computedWatchers[key]
   // 这个watcher就是对应的computed-watcher
   if (watcher) {
     if (watcher.dirty) {  // 在实例化watcher时为true,表示需要计算
       watcher.evaluate()  // 进行计算属性的求值
     }
     // 让计算属性内的响应式数据订阅当前的render-watcher
     if (Dep.target) {  // 当前的watcher,这里是页面渲染触发的这个方法,所以为render-watcher
       watcher.depend()  // 收集当前watcher
     }
     return watcher.value  // 返回求到的值或之前缓存的值
   }
 }
}

//
class Watcher {
 ...

 evaluate () {
   //  计算属性求值
   //  这里会触发computed内部可以访问的响应式数据的get,它们会将当前的computed-watcher作为依赖收集到
   //  自己的dep里。计算完将dirty设置为false,表示已经计算过了
   this.value = this.get()
   this.dirty = false  // 表示计算属性已经计算,不需要再计算
 }
 //
 depend () {
   let i = this.deps.length  // deps内是计算属性内能访问到的响应式数据的dep的数组集合
   while (i--) {
     this.deps[i].depend()  // 让每个dep收集当前的render-watcher
   }
 }
}

computed内的响应式数据会收集computed-watcherrender-watcher两个watcher,当computed内的状态发生变更触发set后,首先通知computed需要进行重新计算,然后通知到视图执行渲染,再渲染中会访问到computed计算后的值,最后渲染到页面。


Quiz Time👇

watch 和 computed 使用上有什么区别?
● computed 是计算一个新的属性,并将该属性挂载到 vm(Vue 实例)上,而 watch 是监听已经存在且已挂载到 vm 上的数据,所以用 watch 同样可以监听 computed 计算属性的变化(其它还有 data、props); ● computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后或者第一次访问 computed 属性,才会计算新的值,而 watch 则是当数据发生变化便会调用执行函数; ● 从使用场景上说,computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据。