# computed原理

当computed依赖的数据发生变化时,computed对应的key会重新计算。否则,则取上次计算的值。
computed具备缓存的功能。

// App.vue
<template>
  <div>
      <p>fullName</p>
  </div>
</template>
<script>
export default {
    data() {
        return {
            firstName: 'hello',
            lastName: 'world'
        }
    },
    computed: {
        fullName() {
            return this.firstName + this.lastName
        }
    }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 初始化computed选项

初始化时,通过initComputed方法来初始化computed选项。

function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options;
  // ...
  // 计算属性watcher
  if (opts.computed) initComputed(vm, opts.computed)
  // ...
}
1
2
3
4
5
6
7
8

循环options中的computed对象,给每个key添加一个watcher(用户watcher)。对于computed watcher,以options选项中的lazy为true作为标识。

同时,将每个key对应的watcher存储到vm._computedWatchers对象中。此时我们创建给fullName这个key创建了一个watcher,暂时命名为watcher_fullName,该watcher的dirty为true。

const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
  // vm._computedWatchers,存储计算属性watcher
  const watchers = vm._computedWatchers = Object.create(null);
  // 是否服务端渲染
  const isSSR = isServerRendering()
  for (const key in computed) {
    const userDef = computed[key]
    // computed的写法,一种是fn;一种是对象,包括get、set方法
    const getter = typeof userDef === 'function' ? userDef : userDef.get;
    if (!isSSR) {
      // 计算属性watcher, lazy为true,getter为计算属性key所对应的function
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }
		if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

调用defineComputed,给每个key,通过defineProperty,自定义其getter、setter。

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}
function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  sharedPropertyDefinition.get = createComputedGetter(key)
  sharedPropertyDefinition.set = noop
  // 通过Object.defineProperty,
  // 定义vm.key的descriptor为`sharedPropertyDefinition`(它的get方法为createComputedGetter的返回值,即computedGetter函数)
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

其中自定义getter,是通过createComputedGetter方法产生。该方法返回一个函数computedGetter,当我们通过vm.fullName访问fullName时,就会触发computedGetter方法。

function createComputedGetter (key) {
  // 计算属性中,某个key的get方法,就是’computedGetter‘函数,对计算属性取值时,就会调用get方法(即’computedGetter‘函数)
  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
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# computed依赖收集

当我们挂载App.vue时,

  1. 初始化当前组件的渲染watcher(我们暂时称之为watcher_app)。在执行new Watcher时,首先将watcher_app入栈,并将Dep.target设置为watcher_app。
stack: [watcher_app];
Dep.target = watcher_app;
1
2
  1. 执行this.getter,即vm._update(vm._render())生成vnode。在执行vm._render过程中,会对fullName取值,此时触发createComputedGetter(fullName)。由于计算属性watcher的lazy为true,初始化时dirty为true,此时调用watcher.evaluate()方法,对fullName进行求值。
function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // dirty为true,计算watcher的value,即执行computed key对应的function
      if (watcher.dirty) {
         watcher.evaluate();
      }
      // 渲染watcher依赖收集
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

watcher的evaluate方法,内部会调用this.get方法,并将dirty设置为false。

  evaluate() {
    // 计算属性watcher计算完成后,设置dirty为false,以后只要依赖的dep没有发生变化,执行计算属性watcher时,就不会重新求值
    this.value = this.get()
    this.dirty = false
  }
1
2
3
4
5
get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
       value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      popTarget()
    }
    return value
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

执行this.get方法时:

1) 首先将当前计算属性watcher入栈,并将Dep.target设置为watcher_fullName

stack: [watcher_app, watcher_fullName];
Dep.target = watcher_fullName;
1
2

2)执行this.getter方法:对于计算属性而言,就是key对应的那个函数。函数内部会对firstName、lastName取值,触发各自的get方法,进行依赖收集。

firstName_dep: { subs:[watcher_fullName] }
lastName_dep:  { subs:[watcher_fullName] }

wathcer_fullName: {deps: [firstName_dep, lastName_dep]}
1
2
3
4
  1. 当前计算属性watcher出栈
stack: [watcher_app];
Dep.target = watcher_app;
1
2
  1. watcher值计算完成后,当前Dep.target仍然有值,为watcher_app。此时调用watcher.depend,循环执行watcher_app中的deps依赖。
function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      //...
      // 渲染watcher依赖收集
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
 }
1
2
3
4
5
6
7
8
9
10
11

循环watcher的deps依赖时,对每个dep依赖,收集当前的渲染watcher(watcher_app).

// watcher depend
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }
1
2
3
4
5
6
7
  1. 渲染watcher出栈,最终依赖的结果为:
// dep
firstName_dep: { subs:[watcher_fullName, watcher_app] }
lastName_dep:  { subs:[watcher_fullName, watcher_app] }

// watcher
wathcer_fullName: {deps: [firstName_dep, lastName_dep]}
watcher_app:      {deps: [firstName_dep, lastName_dep]}
1
2
3
4
5
6
7

整个流程如下:
image.png

# computed更新

当firstName或者lastName发生变化时,触发其get方法,此时执行其watcher数组中,每个watcher的update方法,其基本流程如下:

firstName变化 -> getter -> firstName_dep.notify -> 循环执行water.update -> queueWatcher -> 
nextTick(flushSchedulerQueue) -> watcher.run -> this.get()
1
2

注意:

  1. 在queueWatcher时,由于计算属性watcher(watcher_fullName)在执行一次计算后,dirty设置为false了,所以watcher_fullName并不会进入队列中。只有渲染watcher(watcher_app)进入队列中。
  2. 执行watcher.update时,如果是计算属性watcher,会重置其dirty为true,方便后续计算。
  3. 当执行渲染watcher的run方法时,会重新执行其get方法,重新生成vnode进行页面渲染。在重新生成vnode时,会取vm.fullName的值,然后执行computedGetter重新计算新的值。

image.png