computed 计算属性的底层原理

7 阅读4分钟

在 Vue 中,computed 计算属性是一种非常强大的功能,它允许你根据 Vue 实例的数据(dataprops)来派生出新的值。与普通的函数不同,computed 属性是缓存的,只有当依赖的数据发生变化时,它才会重新计算。这使得 computed 属性在性能方面非常高效。

核心设计目标

  1. 依赖追踪:自动检测计算属性依赖的响应式数据。
  2. 惰性计算:仅在依赖变化时重新计算,避免不必要的计算开销。
  3. 缓存机制:只要依赖未变化,直接返回缓存值,提升性能。

Vue 中的 computed 计算属性通过底层的 getter/setter 实现,它们的工作原理可以通过以下几个步骤理解:

1. 依赖收集

当你在 Vue 中使用 computed 属性时,Vue 会通过其响应式系统将 computed 属性的依赖收集起来。这些依赖通常是组件的响应式数据(如 dataprops)。具体来说,当你访问 computed 属性时,Vue 会执行该属性的 getter 函数,在执行过程中,任何被访问的响应式数据都会被 "收集" 为依赖。

例如:

data() {
  return {
    num1: 1,
    num2: 2
  };
},
computed: {
  sum() {
    return this.num1 + this.num2;
  }
}

在这个例子中,当 sum 被访问时,Vue 会收集 num1num2 作为 sum 的依赖。

2. 缓存机制

Vue 的 computed 属性会根据依赖的数据来自动缓存结果,只有在依赖的数据发生变化时,computed 属性才会重新计算。这意味着,在依赖的数据没有发生变化的情况下,每次访问 computed 属性都会直接返回缓存的结果,而不会重新执行计算。

  • 首次访问时,computed 会执行 getter 函数,并将计算结果缓存。
  • 当依赖的数据发生变化时,Vue 会标记该 computed 属性为(dirty),下一次访问该 computed 时会重新计算。

3. getter 和 setter

每个 computed 属性实际上都有一个 getter 和一个 setter:

  • getter:用于计算并返回 computed 属性的值。当访问 computed 属性时,Vue 会执行 getter 函数来计算值。
  • setter:用于更新依赖的数据。默认情况下,computed 是只读的,只有在你显式定义了 setter 时,才能设置其值。

例如:

computed: {
  sum: {
    get() {
      return this.num1 + this.num2;
    },
    set(value) {
      // 设置 sum 的值时,更新 num1 和 num2
      this.num1 = value - this.num2;
    }
  }
}

在这个例子中,sum 既有 getter 也有 setter。当你给 sum 赋值时,会触发 setter,从而更新 num1 的值。

4. 异步更新和脏数据标记

Vue 使用了一个 异步队列 来批量更新视图,避免了过多的 DOM 更新。在计算属性的实现中,当依赖的数据发生变化时,Vue 会将该计算属性标记为“脏”,但是不会立刻计算,而是等待下一次事件循环(即下一次 DOM 更新周期)再去重新计算它。这样可以避免因为频繁的依赖变化导致不必要的计算。

具体工作流程

  1. 初始化阶段:Vue 会初始化 computed 属性,将其 getter 函数放入一个存储计算结果的对象中。

  2. 访问 computed 属性时

    • Vue 执行 getter 函数来计算 computed 的值。
    • 如果计算过程中访问了某些响应式数据(比如 num1num2),这些数据会被 Vue 的响应式系统收集为依赖。
    • 如果依赖的数据没有变化,返回缓存的值;如果依赖的数据发生变化,则重新计算。
  3. 更新阶段:当依赖的数据发生变化时,Vue 会标记计算属性为“脏”,等待下次更新时重新计算并更新视图。

示例代码:

new Vue({
  el: '#app',
  data() {
    return {
      num1: 1,
      num2: 2
    };
  },
  computed: {
    sum() {
      console.log('Calculating sum');
      return this.num1 + this.num2;
    }
  }
});
  • 第一次访问 sum :会触发计算并打印 "Calculating sum"
  • 之后的访问 sum :如果 num1num2 没有变化,sum 会返回缓存的结果,不会重新计算。

5. 总结

Vue 中 computed 计算属性的底层原理依赖于以下几个关键机制:

  1. 依赖收集:通过 getter 函数收集计算属性依赖的响应式数据。
  2. 缓存机制:计算属性只有在依赖的数据发生变化时才会重新计算,其他情况下直接返回缓存的结果。
  3. 脏数据标记:当依赖的数据发生变化时,计算属性会被标记为“脏”,在下一次更新时重新计算。
  4. getter 和 setter:计算属性通常是只读的,但可以通过定义 setter 来修改依赖的数据。