在 Vue 中,computed
计算属性是一种非常强大的功能,它允许你根据 Vue 实例的数据(data
或 props
)来派生出新的值。与普通的函数不同,computed
属性是缓存的,只有当依赖的数据发生变化时,它才会重新计算。这使得 computed
属性在性能方面非常高效。
核心设计目标
- 依赖追踪:自动检测计算属性依赖的响应式数据。
- 惰性计算:仅在依赖变化时重新计算,避免不必要的计算开销。
- 缓存机制:只要依赖未变化,直接返回缓存值,提升性能。
Vue 中的 computed
计算属性通过底层的 getter/setter 实现,它们的工作原理可以通过以下几个步骤理解:
1. 依赖收集
当你在 Vue 中使用 computed
属性时,Vue 会通过其响应式系统将 computed
属性的依赖收集起来。这些依赖通常是组件的响应式数据(如 data
或 props
)。具体来说,当你访问 computed
属性时,Vue 会执行该属性的 getter 函数,在执行过程中,任何被访问的响应式数据都会被 "收集" 为依赖。
例如:
data() {
return {
num1: 1,
num2: 2
};
},
computed: {
sum() {
return this.num1 + this.num2;
}
}
在这个例子中,当 sum
被访问时,Vue 会收集 num1
和 num2
作为 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 更新周期)再去重新计算它。这样可以避免因为频繁的依赖变化导致不必要的计算。
具体工作流程
-
初始化阶段:Vue 会初始化
computed
属性,将其 getter 函数放入一个存储计算结果的对象中。 -
访问
computed
属性时:- Vue 执行
getter
函数来计算computed
的值。 - 如果计算过程中访问了某些响应式数据(比如
num1
和num2
),这些数据会被 Vue 的响应式系统收集为依赖。 - 如果依赖的数据没有变化,返回缓存的值;如果依赖的数据发生变化,则重新计算。
- Vue 执行
-
更新阶段:当依赖的数据发生变化时,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
时:如果num1
和num2
没有变化,sum
会返回缓存的结果,不会重新计算。
5. 总结
Vue 中 computed
计算属性的底层原理依赖于以下几个关键机制:
- 依赖收集:通过 getter 函数收集计算属性依赖的响应式数据。
- 缓存机制:计算属性只有在依赖的数据发生变化时才会重新计算,其他情况下直接返回缓存的结果。
- 脏数据标记:当依赖的数据发生变化时,计算属性会被标记为“脏”,在下一次更新时重新计算。
- getter 和 setter:计算属性通常是只读的,但可以通过定义 setter 来修改依赖的数据。