探索Vue.js底层源码——计算属性(computed)

计算属性

computed 也是属于组件状态的一部分,所以它初始化同样是在 initState 函数中完成的,initState 函数定义在 src/core/instance/state.js

export function initState (vm: Component) {
  // 定义一个Watcher数组,即用于收集观察者(依赖收集)
  vm._watchers = []
  // 获取vm实例的各种配置
  const opts = vm.$options
  ...
  // 判断当前实例是否存在 computed 选项
  if (opts.computed) initComputed(vm, opts.computed)
  // 判断当前是否存在watch选项且这个watch不是Firefox的Object原型上的watch
  // 火狐的watch原型:nativeWatcher = ({}).watch
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

从 initState 中我们可以看出 computed 选项的初始化是定义在 initComputed 函数中,initComputed 函数同样定义在 src/core/instance/state.js

initComputed

function initComputed (vm: Component, computed: Object) {
  // 创建一个空对象给当前实例的_computedWatchers
  const watchers = vm._computedWatchers = Object.create(null)
  // 调用isServerRendering 判断是否当前是服务端渲染,该函数返回 true or false
  const isSSR = isServerRendering()

  // 遍历 computed 选项
  for (const key in computed) {
    const userDef = computed[key]
    // 获取当前 computed 属性对应的 get 方法
    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) {
      // create internal(内部的) watcher for the computed property.
      // 初始化一个 computed wacher
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation(实例化) here.
    // 将当前computed属性挂在到当前vm实例
    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)
      }
    }
  }
}

defineComputed

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  // 一般都不是服务端渲染所以 shouldCache 为 true
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') { // computed属性对应的get是function
    // sharedPropertyDefinition的get都是通过 createComputedGetter()方法实现
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
  } else { // computed属性对应的set是对象
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    // 如果开发环境,没有设置set,则会 warn 提示没有setter
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  // 给此时计算属性的key添加getter、setter以便用于之后的数据驱动
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

createComputedGetter

function createComputedGetter (key) {
  // 手动创建一个getter函数
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 如果wathcer发生变化
      if (watcher.dirty) {
        // 重新估算wathcer(即重新做依赖收集)
        watcher.evaluate()
      }
      // 如果此时存在订阅者
      if (Dep.target) {
        // 为watcher添加依赖
        watcher.depend()
      }
      // 返回watcher的值
      return watcher.value
    }
  }
}

总结

在开发中需要注意的是,computed 是依赖于其他,但是并不是代表依赖发生变化,组件就一定会更新,而是应该是重新计算后的值与计算前的值不同时,才会触发组件的重新渲染。

发布了140 篇原创文章 · 获赞 16 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_42049445/article/details/103542981