人人都会AI编程

4.5 computed 计算属性:缓存机制与懒执行原理

更新时间:2026-07-11

计算属性是 Vue 中使用频率最高的工具之一。它解决了一个朴素的问题:“某个值需要依赖其他数据计算得出,且计算过程可能较重,我不希望每次用都重新算一遍。” 它的底层实现依赖两个关键行为:缓存和懒执行。

基础用法回顾

<template>
  <div>
    <p>原价:{{ price }} 元</p>
    <p>折后价:{{ discountedPrice }} 元</p>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
const price = ref(100)
const discount = ref(0.8)

const discountedPrice = computed(() => {
  console.log('正在计算折后价')
  return (price.value * discount.value).toFixed(2)
})
</script>

在这个例子中,discountedPrice 只在 pricediscount 变化时才会重新计算。模板中多次使用 discountedPrice,也只会执行一次计算函数。

缓存机制:依赖不变,直接返回上次结果

计算属性内部会维护一个“脏检查”标记。当它被读取时:

  1. 检查自己的依赖项(pricediscount)是否有变化。
  2. 如果依赖项都没变,直接返回上次缓存的结果,跳过函数执行
  3. 如果有任何一个依赖项发生了改变,重新执行计算函数,更新缓存值,并告诉 Vue “我现在干净了”。

这避免了在模板或方法中反复调用计算函数的性能浪费。尤其是当计算逻辑包含数组遍历、正则匹配或复杂聚合运算时,缓存的意义非常明显。

与方法的对比

<template>
  <p>{{ getDiscountPrice() }}</p>
  <p>{{ getDiscountPrice() }}</p>
  <p>{{ getDiscountPrice() }}</p>
</template>

<script setup>
function getDiscountPrice() {
  return (price.value * discount.value).toFixed(2)
}
</script>

如果使用普通方法,每次模板重新渲染(哪怕其他无关数据变化),三个 getDiscountPrice() 都会被执行三次。而计算属性 discountedPrice 在模板中引用三次,只会在首次访问或依赖变化时计算一次,其余两次直接取缓存。

懒执行:不访问就不计算,不浪费资源

计算属性的函数不会在创建时立即执行(这是与 watch 的一大区别)。它只在被读取时才判断是否需要计算。这就叫“懒执行”。

考虑一个比较消耗资源的计算属性,比如对大型列表做过滤和排序:

const filteredList = computed(() => {
  return hugeList.value
    .filter(item => item.status === 'active')
    .sort((a, b) => b.score - a.score)
})

如果当前页面根本没有使用 filteredList(比如它只在某个不常打开的 Tab 中展示),那么即便 hugeList 频繁变化,计算属性的函数也不会被触发——因为根本没有代码去读取它。这就避免了无谓的计算。

只有当渲染函数或其他逻辑显式读取 filteredList.value 时,Vue 才会检查依赖,必要时执行计算函数。这种“按需计算”的设计让计算属性天然适合与条件渲染配合使用。

底层实现关键点(简化理解)

Vue 3 使用 computed 函数创建一个 ComputedRefImpl 实例,其核心逻辑可以概括为:

  1. 内部存储一个 _value 缓存值和一个 _dirty 标记(初始为 true)。
  2. 当读取 .value 时:
  • 如果 _dirtytrue,运行 getter 函数,用返回结果更新 _value,并将 _dirty 置为 false。在执行 getter 时,会通过响应式系统的 track 收集依赖。
  • 如果 _dirtyfalse,直接返回 _value
  1. 当任何一个被收集的依赖项发生改变时,触发 scheduler(不是立即执行计算),仅仅将 _dirty 重新标记为 true。等到下次 .value 被访问时,由于 _dirtytrue,才会重新计算。

这也就是为什么计算属性可以既响应变化,又不会在依赖变化时立刻执行。它只是在依赖变化时“标记脏”,把真正的计算延迟到下一次读取。

什么时候该用计算属性?

  • 需要基于现有数据派生新数据,且该派生逻辑在多处使用。
  • 派生过程有一定计算开销,希望避免重复执行。
  • 派生结果需要随着依赖变化而自动更新。

示例场景:购物车总价、列表过滤与统计、表单字段的联动校验、任何“根据 A 和 B 得出 C”的逻辑。

注意事项

  • 计算属性要作为纯函数使用,不要在 getter 中产生副作用(如修改其他状态、发送请求等)。副作用应放在 watch 或方法中。
  • 计算属性默认是只读的,但也可以提供 set 方法来实现可写计算属性,适用于量少的双向绑定场景。
  • 依赖必须是响应式数据(refreactive 等),否则变化无法被追踪,缓存机制失效。

一句话总结:计算属性 = 自动缓存的计算结果 + 仅在所需时更新。它是处理派生状态最轻松、最高效的方式,使用成本极低,但性能收益显著。