vue 中的 computed 属性的值只有当内部依赖的 data 属性变化时才会重新求值,这是怎么做到的呢。假设传入以下 computed :
1 2 3 4 5 computed: {   ab : function (     return  this .a + 1 ;   } } 
以下是 vue 初始化的操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 export  function  initState  (vm: Component )   vm._watchers = []   const  opts = vm.$options   if  (opts.props) initProps(vm, opts.props)   if  (opts.methods) initMethods(vm, opts.methods)   if  (opts.data) {     initData(vm)   } else  {     observe(vm._data = {}, true  )   }   if  (opts.computed) initComputed(vm, opts.computed)   if  (opts.watch && opts.watch !== nativeWatch) {     initWatch(vm, opts.watch)   } } 
initComputed 做到的事情是实例化 Watcher 以及将属性挂载到实例上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 function  initComputed  (vm: Component, computed: Object  )      const  watchers = vm._computedWatchers = Object .create(null )      const  isSSR = isServerRendering()      for  (const  key in  computed) {          const  userDef = computed[key]     const  getter = typeof  userDef === 'function'  ? userDef : userDef.get     if  (process.env.NODE_ENV !== 'production'  && getter == null ) {       warn(         `Getter is missing for computed property "${key} ".` ,         vm       )     }               if  (!isSSR) {              watchers[key] = new  Watcher(          vm,         getter || noop,         noop,         { lazy : true  }        )     }     if  (!(key in  vm)) {              defineComputed(vm, key, userDef)      } else  if  (process.env.NODE_ENV !== 'production' ) {              if  (key in  vm.$data) {         warn(`The computed property "${key} " is already defined in data.` , vm)       } else  if  (vm.$options.props && key in  vm.$options.props) {         warn(`The computed property "${key} " is already defined as a prop.` , vm)       }     }   } } 
可以看到 Watcher 构造器接受了一个参数 { lazy: true },下边进入 Watcher 介绍该参数起的作用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 export  default  class  Watcher    constructor  (     vm: Component,     expOrFn: string | Function ,     cb: Function ,     options?: ?Object ,     isRenderWatcher?: boolean    ) {    this .vm = vm     vm._watchers.push(this )     if  (options) {       this .deep = !!options.deep       this .user = !!options.user       this .lazy = !!options.lazy       this .sync = !!options.sync       1efore = options.before     } else  {       this .deep = this .user = this .lazy = this .sync = false      }     this .dirty = this .lazy                this .value = this .lazy ? undefined  : this .get()   } } 
此时开头假设的this.ab的值为undefined,这里可以看到计算属性在初始化的时候不计算其值 。然后代码执行到defineComputed(vm, key, userDef):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 export  function  defineComputed  (  target: any,   key: string,   userDef: Object  | Function       const  shouldCache = !isServerRendering()   if  (typeof  userDef === 'function' ) {          sharedPropertyDefinition.get = shouldCache     ? createComputedGetter(key)     : createGetterInvoker(userDef)     sharedPropertyDefinition.set = noop   } else  {          sharedPropertyDefinition.get = userDef.get     ? shouldCache && userDef.cache !== false        ? createComputedGetter(key)       : createGetterInvoker(userDef.get)     : noop     sharedPropertyDefinition.set = userDef.set || noop   }   Object .defineProperty(target, key, sharedPropertyDefinition) } 
在createComputedGetter中用到了watcher.dirty,而其初始值由前面传入的参数{ lazy: true }决定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function  createComputedGetter  (key )   return  function  computedGetter  (     const  watcher = this ._computedWatchers && this ._computedWatchers[key]     if  (watcher) {              if  (watcher.dirty) {         watcher.evaluate()       }       if  (Dep.target) {                  watcher.depend()       }       return  watcher.value     }   } } 
然后执行Object.defineProperty(target, key, sharedPropertyDefinition),代码执行到这里其实是把计算属性的 setter 和 getter 创建好了。所以,计算属性如果未被消费不会计算值 ,其值始终是 undefined。
当计算属性被用到时(例如在 render 或 methods 中),此时会触发computedGetter,进而执行evaluate:
1 2 3 4 function  evaluate  (  this .value = this .get()   this .dirty = false  } 
调用this.get()会进行依赖收集,最后返回计算属性的值,此时终于获取到了计算属性的值。这里为什么要将this.dirty设为false呢,是为了实现计算属性基于它的依赖进行缓存的,也就依赖没变时,不重新计算,而是使用缓存。watcher.lazy始终为false,也就永远不会执行watcher.evaluate(),所以这个计算属性永远不会重新求值,一直使用上一次获得的值。
1 2 3 if  (watcher.dirty) {  watcher.evaluate() } 
那什么时候会重新求值呢,当watcher.dirty为true时,怎么改变这个值呢,当依赖变更的时候,即在watcher.update()中:
1 2 3 4 5 6 7 8 9 update () {   if  (this .lazy) {     this .dirty = true    } else  if  (this .sync) {     this .run()   } else  {     queueWatcher(this )   } } 
以开头的假设为例,当this.a改变时,将触发dep.notify(),然后执行该dep中所有 watcher的update方法,其中就包括this.ab的watcher.update()(这个watcher是在前面的watcher.depend()中添加的),此时watcher.dirty会被改变为true,因此当再次取this.ab的值时,会重新计算。
计算属性的依赖变更如何触发渲染 如果计算属性在模板或 render 函数中被使用,在触发计算watcher.update()后,会触发渲染watcher.update(),此时取this.ab,会得到重新计算后的值。渲染watcher.update()在计算watcher.update()后执行呢,因为如果在之前执行得到的this.ab会是旧值(因为此时watcher.dirty还为false)。这涉及到全局的 Dep.target 状态是用一个栈 targetStack 来保存。
假设在 render 函数中使用了this.ab:
1 2 3 render () {   return  <div > {this.ab}</div >  } 
当执行 render 函数时,此时Dep.target指向渲染watcher,targetStack = [渲染watcher],执行this.a + 1,读取到了this.a,触发a的get方法,此时Dep.target指向计算watcher,targetStack = [渲染watcher,计算watcher],调用dep.depend()收集计算watcher作为依赖,
1 2 3 4 5 6 depend () {   if  (Dep.target) {      Dep.target.addDep(this )   } } 
经历了这样的一个收集的流程后,此时的一些状态:
1 2 3 4 5 6 7 8 {   deps : [ a的dep ],   dirty : false ,    value : 2 ,   getter : ƒ ab(),   lazy : true  } 
1 2 3 4 {   subs : [ ab的计算watcher ] } 
此时求值结束,回到计算watcher的get方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 get () {   pushTarget(this )   let  value   const  vm = this .vm   try  {     value = this .getter.call(vm, vm)   } finally  {          popTarget()   }   return  value } 
进入this.ab的getter函数:
1 2 3 4 5 6 7 8 9 10 11 Object .defineProperty(vm, 'ab' , {   get (     if  (watcher.dirty) {       watcher.evaluate()     }     if  (Dep.target) {        watcher.depend()     }     return  watcher.value   } }) 
1 2 3 4 5 6 7 depend () {   let  i = this .deps.length   while  (i--) {     this .deps[i].depend()   } } 
因为 ab 的计算watcher 的 deps 里保存了 a 的 dep,因此又会调用 a 的 dep.depend(),会把渲染watcher 存放进自身的 subs 中,最后a的 dep 如下:
1 2 3 {   subs : [ ab的计算watcher,渲染watcher ] } 
一切初始化已经完成,若改变a的值,将触发 a 的 dep.notify:
1 2 3 4 5 6 notify () {   const  subs = this .subs.slice()   for  (let  i = 0 , l = subs.length; i < l; i++) {     subs[i].update()   } } 
以上代码依次调用了计算watcher.update()和渲染watcher.update(),保证渲染watcher.update()在计算watcher.update()后执行,此时 render 函数中能获取到最新的计算属性的值。
总结 
通过改变this.dirty的值来实现缓存,当this.dirty === true时,再次访问才会执行watcher.evaluate()获得新值。 
计算属性的依赖变更时触发计算watcher.update(),执行this.dirty = true,使得再次获取计算属性值时被重新计算。