2

computed的计算属性有缓存机制,只有当其依赖的响应式数据发生变化时才会清空缓存重新计算结果

其缓存机制本质是通过一个dirty属性控制的,只有dirty为true时才会重新计算结果替换缓存。
dirty只有当其响应式数据发送变化时才会设置为true,重新计算后会再次被设置为false

initComputed:
  1. 遍历我们定义的computed属性,为每一个computed属性创建一个内部的监视器Watcher,保存在vm实例的_computedWatchers中,参数传递了一个lazy为true。
  2. 如果计算属性与已定义的data或者props中的名称不冲突则会执行defineComputed方法。
const computedWatcherOptions = { lazy: true }

function initComputed (vm: Component, computed: Object) {
  const watchers = vm._computedWatchers = Object.create(null)

  for (const key in computed) {
    const userDef = computed[key]
    /*计算属性可能是一个function,也有可能设置了get以及set的对象。*/
    let getter = typeof userDef === 'function' ? userDef : userDef.get
    /*
      为计算属性创建一个内部的监视器Watcher,保存在vm实例的_computedWatchers中
      这里的computedWatcherOptions参数传递了一个lazy为true,会使得watch实例的dirty为true
    */
    watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)

    /*组件正在定义的计算属性已经定义在现有组件的原型上则不会进行重复定义*/
    if (!(key in vm)) {
      /*定义计算属性*/
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      /*如果计算属性与已定义的data或者props中的名称冲突则发出warning*/
      /* ... */
    }
  }
}
defineComputed:
  1. 通过createComputedGetter将每一个computed属性包装成了computedWatcher实例
  2. 再将每个computedWatcher实例通过Object.defineProperty再次进行包装,在使用时触发了get,get函数会拦截判断computedWatcher实例dirty的值,为true时则触发watcher类的evaluate函数,该函数用来获取值最后再关闭dirty,dirty是false直接返回第一次获取的值。
export function defineComputed (target: any, key: string, userDef: Object | Function) {
  if (typeof userDef === 'function') {
    /*创建计算属性的getter*/
    sharedPropertyDefinition.get = createComputedGetter(key)
    /*
      当userDef是一个function的时候是不需要setter的,所以这边给它设置成了空函数。
      因为计算属性默认是一个function,只设置getter。
      当需要设置setter的时候,会将计算属性设置成一个对象。参考:https://cn.vuejs.org/v2/guide/computed.html#计算-setter
    */
    sharedPropertyDefinition.set = noop
  } else {
    /*get不存在则直接给空函数,如果存在则查看是否有缓存cache,没有依旧赋值get,有的话使用createComputedGetter创建*/
    sharedPropertyDefinition.get = userDef.get
      ? userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop
    /*如果有设置set方法则直接使用,否则赋值空函数*/
    sharedPropertyDefinition.set = userDef.set
      ? userDef.set
      : noop
  }
  /*defineProperty上getter与setter*/
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

/*创建计算属性的getter*/
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      /* 在计算属性中的依赖发生改变的时候dirty会变成true,在get的时候重新计算计算属性的输出值 */
      if (watcher.dirty) {
        watcher.evaluate()
      }
      /*依赖收集*/
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}
/*Watcher中的evaluate函数*/
evaluate () {
  this.value = this.get()
  this.dirty = false
}

所以computed具有缓存的特性,只有当所依赖的数据发生变化,会触发Object.defineProperty set里的notify方法通知所有的观察者,notify又调用update,update里如果lazy为true,则会将dirty置为true,一旦dirty为true,计算属性在get的时候就会重新计算。

Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      /* 省略 */
    },
    set: function reactiveSetter (newVal) {
      /* 省略 */

      /*dep对象通知所有的观察者*/
      dep.notify()
    }
  })

 /* dep/notify */
 notify () {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }

 /* watcher/update */
 update () {
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

结尾

我是周小羊,一个前端萌新,写文章是为了记录自己日常工作遇到的问题和学习的内容,提升自己,如果您觉得本文对你有用的话,麻烦点个赞鼓励一下哟~

小绵羊
70 声望517 粉丝