vue cumputed原理

new Vue的时候:执行 initComputed (vm, computed)

function initComputed (vm, computed) {
...
for (var key in computed) {
    var userDef = computed[key];
    var getter = typeof userDef === 'function' ? userDef : userDef.get;
    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop, // 这个值就是用户定义的computed: {key: val} 的val
        noop,
        computedWatcherOptions
      );
}
...
}

所以本质上computed是一个watcher

 constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
...
 if (typeof expOrFn === 'function') {
// 这里的expOrFn就是用户定义的computed{key: val}中的val
      this.getter = expOrFn
    } 
...

}

假设用户computed中定义为

用户的computed中: {
    fullName () {
     // 'aa' + 'bb'
     return this.firstName + this.lastName
  }
}

假设这个时候firstName变成了'a' , lastName变成了'abb'
这两个值的变化都会触发派发更新
会触发watcher.update,当前的watcher为computed

 update () {
    /* istanbul ignore else */
    if (this.computed) {
      // A computed property watcher has two modes: lazy and activated.
      // It initializes as lazy by default, and only becomes activated when
      // it is depended on by at least one subscriber, which is typically
      // another computed property or a component's render function.
      if (this.dep.subs.length === 0) {
        // 当你并没有使用过这个computed的时候,其实就可以啥都不做
        // In lazy mode, we don't want to perform computations until necessary,
        // so we simply mark the watcher as dirty. The actual computation is
        // performed just-in-time in this.evaluate() when the computed property
        // is accessed.
        this.dirty = true
      } else {
        // In activated mode, we want to proactively perform the computation
        // but only notify our subscribers when the value has indeed changed.
       // 有使用的case
        this.getAndInvoke(() => {
          this.dep.notify()
        })
      }
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

上一步分析到:会执行

this.getAndInvoke(() => {
  this.dep.notify()  // this为当前的wacther
})

getAndInvoke

getAndInvoke (cb: Function) {
    const value = this.get()
    if (
    //  这里会做一个比较,相同就是会走缓存,不会派发更新,但是会每次求值
      value !== this.value ||
      // Deep watchers and watchers on Object/Arrays should fire even
      // when the value is the same, because the value may
      // have mutated.
      isObject(value) ||
      this.deep
    ) {
      // set new value
      const oldValue = this.value
      this.value = value
      this.dirty = false
      if (this.user) {  
      // 我们在new Vue({watch: {}})写的watch
        try {
          cb.call(this.vm, value, oldValue)
        } catch (e) {
          handleError(e, this.vm, `callback for watcher "${this.expression}"`)
        }
      } else {
         // 这里执行上一步的 () => {this.dep.notify()}
        cb.call(this.vm, value, oldValue)
        // 这里会派发更新,像普通的data.fullName值一样,会重新渲染vue
      }
    }
  }

wacther.get

get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

结论:

computed初始化的时候做了一个get求值,执行了一次用户定义的表达式,这个computed watcher会订阅firstName和lastName的(执行过程中有其他的响应式数据也会订阅),firtName或者lastName变化会触发computed表达式执行,但是当新值和旧值相等时,不会触订阅了fullName的watcher更新。所以会有缓存一说。

附录1:响应式梳理

响应式梳理
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容