返回

直击源头,解析 Vue 计算属性的前世今生

前端

源码中的计算属性
Vue 的计算属性本质上是响应式函数,这可不是我们胡说的,而是 Vue 官方给出的定义:计算属性就是“返回一个被侦听响应式值的函数。”换句话说,只要我们定义了一个计算属性,Vue 就会对这个属性依赖的响应式数据进行侦听。一旦侦听到这些数据发生了变化,Vue 便会自动调用该计算属性对应的函数,重新计算结果并更新 UI。

而在 Vue 的源码中,计算属性被定义为一个特殊的函数,该函数以 getter 和 setter 的形式存在。其中,getter 负责计算属性的值,setter 则负责更新属性的值。

// src/core/instance/computed.js

export function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null)

  // computed properties are just getters during SSR
  const isSSR = isServerRendering()

  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (getter === undefined) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }

    if (!isSSR) {
      // create internal watcher for computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are reactive
    if (!(key in vm)) {
      defineReactive(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      warn(
        `The computed property "${key}" is already defined in data.`,
        vm
      )
    }
  }
}

缓存机制

既然计算属性是一个函数,那么我们可以想象它每次都会被执行,这样的话,只要它的依赖属性一变,它就会重新计算,然后更新 UI。但问题是,这样的方式开销很大,也会导致性能下降。所以,Vue 在计算属性上做了一些优化,引入了缓存机制。

当计算属性第一次被调用时,Vue 会计算它的值并将其缓存起来。当计算属性的依赖属性发生变化时,Vue 不会立即重新计算该属性,而是先检查缓存。如果缓存中已经存在了该属性的值,Vue 就会直接从缓存中取出并使用。只有当缓存中不存在该属性的值时,Vue 才会重新计算它。

// src/core/instance/computed.js

export function computedWatcherOptions: Object {
  get: boolean
}

export function simpleNormalizeWatcher (
  userDef: WatcherOptions | Function,
  vm: Component
): WatcherOptions {
  if (!userDef) {
    return {}
  }

  if (typeof userDef === 'function') {
    return {
      get: userDef
    }
  }

  return userDef
}

更新机制

当计算属性的依赖属性发生变化时,Vue 会重新计算该属性。计算属性的更新机制分为以下几个步骤:

  1. Vue 会创建一个新的 watcher,该 watcher 专门负责监听计算属性的依赖属性。
  2. 当计算属性的依赖属性发生变化时,该 watcher 会被触发,并执行计算属性的 getter 函数。
  3. 计算属性的 getter 函数会重新计算该属性的值,并将新值返回给 Vue。
  4. Vue 会将计算属性的新值更新到该属性的响应式对象中。
  5. Vue 会触发该计算属性的依赖属性的更新,从而更新这些属性对应的视图。
// src/core/instance/computed.js

export function computedWatcherOptions: Object {
  lazy: true
}

实例范畴

计算属性的实例范畴指的是该属性所在的组件实例。计算属性只能访问其所在组件实例的数据,而不能访问其他组件实例的数据。

// src/core/instance/computed.js

export function computed (vm: Component, key: string, userDef: Object | Function): any {
  const watcher = vm._computedWatchers && vm._computedWatchers[key]
  if (watcher) {
    if ('value' in watcher) {
      return watcher.value
    }
    const computed = watcher.get()
    if (!isRef(computed)) {
      watcher.value = computed
    }
    return computed
  }
}

总结

本文对 Vue 源码中计算属性的实现进行了详细的解析,从缓存机制到更新机制,从 getter 和 setter 到实例范畴,希望能帮助大家更好地理解计算属性。