initComputed 函数调用执行逻辑如下:

function initState (vm) {
  vm._watchers = [];
  var 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);
  }
}

首先,会调用 initData 对 data 数据做observe、new Observer、defineReactive 等一系列操作,对 data 中的数据做 getter 和 setter 操作。传送门➡️ (opens new window)

接着,会调用 initComputed 对 computed 数据进行初始化。

# initComputed 函数

源码如下:

var computedWatcherOptions = { lazy: true }; // computed watcher 有缓存机制,lazy 为缓存控制标志

function initComputed (vm, computed) {
  // $flow-disable-line
  var watchers = vm._computedWatchers = Object.create(null); // 用于收集 computed watcher
  // computed properties are just getters during SSR
  var isSSR = isServerRendering();

  for (var key in computed) {
    var userDef = computed[key];
    var getter = typeof userDef === 'function' ? userDef : userDef.get;
    // computed getter null 值判断
    if (getter == null) {
      warn(
        ("Getter is missing for computed property \"" + key + "\"."),
        vm
      );
    }

    if (!isSSR) {
      // 为每个 computed 属性实例化一个 watcher,即:computed-watcher
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      );
    }

    if (!(key in vm)) {
      // 调用 defineComputed 函数,设置每个 computed 属性的 setter 和 getter,做代理处理
      defineComputed(vm, key, userDef);
    } else {
      // 断言处理
      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);
      } else if (vm.$options.methods && key in vm.$options.methods) {
        warn(("The computed property \"" + key + "\" is already defined as a method."), vm);
      }
    }
  }
}

computed 的结果有缓存机制,通过实例化 watcher 时设置参数 lazy 为 true 进行设置。

计算属性的结果会被缓存,除非依赖的响应式 property 变化才会重新计算。注意,如果某个依赖 (比如非响应式 property) 在该实例范畴之外,则计算属性是不会被更新的。

initComputed 函数的执行逻辑为:

  • 定义 vm._computedWatchers,所有的 computed-watcher 都会被收集在这里。
  • 获取 computed 的 getter 函数,如果该 computed 属性的值是函数的话直接把该函数作为 getter,如果是对象的话,则需要设置对象的 get 函数。有以下两种写法:
export default {
  computed: {
    // 函数写法
    sum() {
      return this.num1 + this.num2
    },
    // 对象写法
    sum2: {
      get: function () {
        return this.num1 + this.num2
      }
    }
  }
}
  • 接着,为每个 computed 属性实例化一个 watcher,即:computed-watcher。
  • 调用 defineComputed 函数,设置每个 computed 属性的 setter 和 getter,做拦截处理。

# defineComputed 函数

function defineComputed (
  target,
  key,
  userDef
) {
  var shouldCache = !isServerRendering();
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef);
    sharedPropertyDefinition.set = noop;
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop;
    sharedPropertyDefinition.set = userDef.set || noop;
  }
  if (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 函数的作用是:调用 Object.defineProperty 设置每个 computed 属性的 setter 和 getter,为每个 computed 属性做代理处理。getter 的核心是通过调用 createComputedGetter 为每个 computed 属性生成 getter 函数,setter 函数如果没有手动定义的话则会设置为空函数。

# createComputedGetter 函数

function createComputedGetter (key) {
  return function computedGetter () {
    var watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate();
      }
      if (Dep.target) {
        watcher.depend();
      }
      return watcher.value
    }
  }
}

createComputedGetter 函数是 computed 逻辑中最重要的一环。

当获取 computed 数据时,会触发 computed 的 getter,即:createComputedGetter 返回的 computedGetter 函数;接着,判断 computed-watcher 的 dirty 值是否为 true,如果为 true,表示缓存的数据已经脏了,需要重新计算;然后,如果当前有 watcher 的话,则会调用 watcher.depend,作用是建立 data 与 render-watcher 的联系;最后,返回 watcher.value,也就是当前 computed 属性的值。

# watcher.evaluate

watcher.evaluate 的作用是调用 computed-watcher 的 get 函数,即:触发 computed 的 getter 函数,重新计算 computed 的值,并将 dirty 设置为 false。函数源码如下:

Watcher.prototype.evaluate = function evaluate () {
  this.value = this.get();
  this.dirty = false;
};

# watcher.depend

watcher.depend 的作用是建立 computed-watcher 的所有 deps(即:依赖的所有 data)与 render-watcher 的依赖关系。函数源码如下:

Watcher.prototype.depend = function depend() {
  var i = this.deps.length;
  while (i--) {
    this.deps[i].depend();
  }
};

函数很简单,通过遍历 computed-watcher 实例上的 deps 列表,调用每个列表项 dep 上的 depend 方法。

那么问题来了,this.deps 是什么?data 数据怎么建立与 render watcher 的关系?

# 流程

用下面例子来说明:

<template>
  <div id="app">{{ sum }}</div>
</template>

<script>
export default {
  data() {
    return {
      num1: 1,
      num2: 2
    }
  },
  computed: {
    sum() {
      return this.num1 + this.num2
    }
  }
}
</script>

首先,当 render-watcher 实例化的时候,Dep.target 为 render-watcher,会触发 computed 中属性 sum 的 getter 函数,即以下函数。

function createComputedGetter (key) {
  return function computedGetter () {
    var watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate();
      }
      if (Dep.target) {
        watcher.depend();
      }
      return watcher.value
    }
  }
}

接着,获取当前的 computed-watcher,由于是首次获取值,所以 dirty 为 true,会调用 watcher.evaluate 进行值的计算,watcher.evaluate 内部会触发 computed-watcher 的 get 函数。

computed-watcher 的 get 函数:首先会将 Dep.target 设置为 computed-watcher,接着调用 computed 的 getter 函数,内部会获取 data 数据(例子中是 num1 和 num2),进而触发 data 数据的 getter 函数,然后,将 Dep.target 恢复为 render-watcher,最后调用 cleanupDeps 函数。

watcher.evaluate 达到目的:

  • 在 dep 的 subs 中保存当前的 computed-watcher。
  • 在 computed-watcher 的 deps 保存所有新的 dep。到此,this.deps 为保存 num1 和 num2 的 dep 实例列表,每个 dep 实例内部的 subs 中又保存着 computed-watcher。

接着,调用 watcher.depend,此时的 Dep.target 恢复为 render-watcher,再看下函数内部逻辑:

Watcher.prototype.depend = function depend() {
  var i = this.deps.length;
  while (i--) {
    this.deps[i].depend(); // 调用 dep 实例的 depend 函数
  }
};
class Dep {
  // ...

  depend(info?: DebuggerEventExtraInfo) {
    if (Dep.target) {
      Dep.target.addDep(this)
      if (__DEV__ && info && Dep.target.onTrack) {
        Dep.target.onTrack({
          effect: Dep.target,
          ...info
        })
      }
    }
  }

  // ...
}

这里对 computed-watcher 实例内部的 deps 进行遍历,然后调用每个 dep 的 depend 方法。由于此时的 Dep.target 为 render-watcher,所以会将 render-watcher 添加到每个 dep 的 subs 中,最终建立 data 与 render-watcher 的联系。

当 computed 属性中依赖的 data 数据更新时,流程是这样的:

  • 触发 data 数据的 setter,通知该 data 数据的所有 watcher 更新数据。
  • 先通知 computed-watcher。computed-watcher 不会立刻进行数据更新,而是将 dirty 设置为 true,表示当前 computed 的数据已经脏了,需要重新计算。
  • 再通知 render-watcher。render-watcher 会重新获取 computed 的值,进而触发 computed 的 getter 函数。
  • 进入 computedGetter 函数,由于 watcher.dirty 为 true,会执行 watcher.evaluate 重新计算 computed 的值,进而获取到最新的值。