极客时间返利平台,你可以在上边通过山月的链接购买课程,并添加我的微信 (shanyue94) 领取返现。
每天晚上九点 B站讲解前端工程化直播,并解答相关问题。

# vue 中 computed 的原理是什么

Issue

欢迎在 Gtihub Issue 中回答此问题: Issue 91 (opens new window)

Author

回答者: wython (opens new window)

要讲清楚,computed 原理,首先得讲 vue 响应式原理,因为 computed 的实现是基于 Watcher 对象的。 那么 vue 的响应式原理是什么呢,众所周知,vue 是基于 Object.defineProperty 实现监听的。在 vue 初始化数据 data 和 computed 数据过程中。会涉及到以下几个对象:

  1. Observe 对象
  2. Dep 对象
  3. Watch 对象 Observe 对象是在 data 执行响应式时候调用,因为 computed 属性基于响应式属性,所以其不需要创建 Observe 对象。 Dep 对象主要功能是做依赖收集,有个属性维护多个 Watch 对象,当更新时候循环调用每个 Watch 执行更新。 Watch 对象主要是用于更新,而且是收集的重点对象。

这里谈到 computed 计算属性,首先要知道,其有两种定义方式,一种是方法,另一种是 get,set 属性。而且,其内部监听的对象必须是已经定义响应式的属性,比如 data 的属性 vuex 的属性。

vue 在创建 computed 属性时候,会循环所有计算属性,每一个计算属性会创建一个 watch,并且在通过 defineProperty 定义监听,在 get 中,计算属性工作是做依赖收集,在 set 中,计算属性重要工作是重新执行计算方法,这里需要多补充一句,因为 computed 是懒执行,也就是说第一次初始化之后,变不会执行计算,下一次变更执行重新计算是在 set 中。

另一个补充点是依赖收集的时机,computed 收集时机和 data 一样,是在组件挂载前,但是其收集对象是自己属性对应的 watch,而 data 本身所有数据对应一个 watch。

以下附计算属性源码验证说法:

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 (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.
      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.
    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
        );
      }
    }
  }
}

可以看到,在执行 new Watcher 之前,会对计算属性做判断,判断其是否为函数,如果不是则取 getter。这是因为计算属性有两种定义方式。之后第二步是执行 deineCoumputed。这一步只是简单的调用 defineProterty 我就不贴代码了。

关于计算属性的 getter 和 setter 定义如下: 重点关注 get 的懒加载部分,和 Watcher 的定义

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

function createGetterInvoker(fn) {
  return function computedGetter() {
    return fn.call(this, this);
  };
}
Last Updated: 11/27/2021, 10:11:48 AM