Vue源码窥探之 computed

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35534823/article/details/88421688

让我们先从一些面试题来窥探 computed 吧。

computedwatch 的区别 ?

computed 是自动监听依赖值的变化,可以监听多个依赖值,从而动态返回内容。
watch 是监听一个值或一个对象,然后当依赖发生变化的时候,可以执行回调函数或者异步操作等。
通常来说仅仅是需要动态值就使用 computed ,如果需要在值改变后执行一些业务逻辑,就使用 watch

computedmethod 的区别?

computed 是一个 lazy watch ,只有当依赖发生变化的时候才会重新计算。
method 是当每次触发重新渲染的时候,都会再此执行函数。

Vue computed 实现

  1. 在 new Vue() 的时候,会先执行 initState

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 /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}
  1. 可以在上面的函数里看到会调用 initComputed 方法,那我们看一下这个方法的实现
const computedWatcherOptions = { lazy: true }

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

  for (const key in computed) {
  	// 拿到 getter
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    // 为 computed 的属性创建内部watcher,注意传入的 computedWatcherOptions
    watchers[key] = new Watcher(
      vm,
      getter || noop,
      noop,
      computedWatcherOptions
    )
    defineComputed(vm, key, userDef)
  }
}

这里我们可以看到,我们先声明一个空对象,再拿到 computedgetter ,如果是 function 就取该函数,否则就取其 get。接着我们创建一个 Watcher 实例,注意第四个参数是 { lazy: true }

  1. 然后我们看这个 Watcher 类,在 core/observer/watcher 中定义,它接收5个参数,因为我们这个不是 渲染watcher ,所有第五个参数我们不需要传入
    在这里插入图片描述
    可以看到我们在 initComputed 中实例化 Watcher 的时候,传入了一个 computedWatcherOptions ,该参数作为 options ,可以看到,this.lazythis.dirty 都为 true
    然后最关键的是 this.value = this.lazy ? undefined : this.get() 。这里和平常的 watcher 是不同的,这里如果 this.lazy = true ,那么其是不会去求值的。 那什么之后执行我们定义在 computed 中的方法呢?因为这里的逻辑已经执行完毕,initComputed 中只剩一个 defineComputed 方法待执行了。答案自然就在这里。

  2. 那我们接着看 defineComputed 的实现

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : userDef
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop
    sharedPropertyDefinition.set = userDef.set
      ? userDef.set
      : noop
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

可以看到, defineComputed 方法的第一步是创建一个完整的 sharedPropertyDefinition ,因为我们只是给 computed 设置一个方法,并非自定义 getter, 并且是非服务端渲染,所以进入 sharedPropertyDefinition.get = createComputedGetter(key)sharedPropertyDefinition.set = noop , 最后用新的 sharedPropertyDefinitioncomputed 的这个 key 挂在到 vm上,当你访问这个属性的时候,就会执行 sharedPropertyDefinition 中的 get 方法。这个方法其实就是 createComputedGetter(key) ,所以我们有必要看看 createComputedGetter 到底是个什么?

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
    }
  }
}

createComputedGetter 返回了一个 computedGetter 函数,也就是说我们访问计算属性这个 key 的时候,会执行 computedGetter 函数,因为我们在 initComputed 的时候执行过

const watchers = vm._computedWatchers = Object.create(null)

所以我们这里可以拿到 watcher,因为我们在实例化的时候传入的 lazy = true ,所以这里会执行 watcher.evaluate 方法

evaluate () {
  this.value = this.get()
  this.dirty = false
}

这里才会执行 this.get() ,进行求值。在我们执行this.get的时候,会为 Dep.target 赋值,所以就会执行 watcher.depend() ,这里会对计算属性进行依赖收集,订阅该计算属性中的所有依赖值,当他们发生变化的时候就会 notify 该计算属性重新计算。

当所依赖的值改变的时候,会通知计算属性,执行 update 方法,这时才会把 this.dirty = true ,如果依赖值没有改变,那么多次访问该计算属性,不会重新计算,返回的是上一次的计算值。

所以, computed 是一个 lazy watch

猜你喜欢

转载自blog.csdn.net/qq_35534823/article/details/88421688