Vue3 计算属性 vs 函数方法详解(缓存机制+使用方式)

175 阅读3分钟

Vue3 计算属性 vs 函数方法详解

参考 计算属性 | Vue.js

核心概念理解

计算属性(computed)是什么?

计算属性是基于响应式数据自动计算得出的值,当依赖的数据发生变化时,它会自动重新计算。

函数方法(methods)是什么?

函数方法就是普通的 JavaScript 函数,每次调用时都会执行。

核心区别

1. 缓存机制

<template>
  <div>
    <!-- 计算属性:有缓存,只计算一次 -->
    <p>计算属性: {{ expensiveComputed }}</p>
    <p>计算属性: {{ expensiveComputed }}</p>
    <p>计算属性: {{ expensiveComputed }}</p>
    
    <!-- 函数方法:无缓存,调用几次执行几次 -->
    <p>函数方法: {{ expensiveMethod() }}</p>
    <p>函数方法: {{ expensiveMethod() }}</p>
    <p>函数方法: {{ expensiveMethod() }}</p>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const count = ref(0)

// 计算属性:有缓存
const expensiveComputed = computed(() => {
  console.log('计算属性执行了!') // 只会打印一次
  return count.value * 2
})

// 函数方法:无缓存
const expensiveMethod = () => {
  console.log('函数方法执行了!') // 会打印三次
  return count.value * 2
}

// 测试
setTimeout(() => {
  count.value = 10 // 只有当依赖变化时,计算属性才会重新计算
}, 1000)
</script>

2. 使用方式

<template>
  <div>
    <!-- 计算属性:像变量一样使用(无括号) -->
    <p>{{ fullName }}</p>
    <p>{{ isAdult }}</p>
    
    <!-- 函数方法:像函数一样调用(有括号) -->
    <p>{{ getFullName() }}</p>
    <p>{{ checkAdult() }}</p>
    
    <!-- 函数可以传参 -->
    <p>{{ greet('Hello') }}</p>
    <button @click="handleClick('按钮1')">点击我</button>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const firstName = ref('张')
const lastName = ref('三')
const age = ref(25)

// 计算属性:基于响应式数据自动计算
const fullName = computed(() => {
  return firstName.value + lastName.value
})

const isAdult = computed(() => {
  return age.value >= 18
})

// 函数方法:普通函数
const getFullName = () => {
  return firstName.value + lastName.value
}

const checkAdult = () => {
  return age.value >= 18
}

// 函数可以接收参数
const greet = (greeting) => {
  return `${greeting}, ${fullName.value}!`
}

const handleClick = (buttonName) => {
  alert(`点击了${buttonName}`)
}
</script>

完整示例对比

<template>
  <div>
    <h2>购物车示例</h2>
    
    <div>
      <h3>商品列表</h3>
      <div v-for="item in products" :key="item.id">
        <span>{{ item.name }} - ¥{{ item.price }}</span>
        <button @click="addToCart(item)">加入购物车</button>
      </div>
    </div>
    
    <div>
      <h3>购物车</h3>
      <div v-for="item in cart" :key="item.id">
        <span>{{ item.name }} x {{ item.quantity }}</span>
      </div>
      
      <!-- 计算属性:购物车总价 -->
      <p>总价(计算属性): ¥{{ totalPrice }}</p>
      
      <!-- 函数方法:购物车总价 -->
      <p>总价(函数方法): ¥{{ calculateTotalPrice() }}</p>
      
      <!-- 函数方法:带参数的计算 -->
      <p>折扣后价格: ¥{{ calculateDiscountedPrice(0.8) }}</p>
    </div>
    
    <button @click="updateProductPrice">涨价</button>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

// 商品数据
const products = ref([
  { id: 1, name: '苹果', price: 5 },
  { id: 2, name: '香蕉', price: 3 },
  { id: 3, name: '橙子', price: 4 }
])

// 购物车
const cart = ref([
  { id: 1, name: '苹果', quantity: 2 },
  { id: 2, name: '香蕉', quantity: 3 }
])

// ========== 计算属性 ==========
// 购物车总价 - 自动计算,有缓存
const totalPrice = computed(() => {
  console.log('计算属性执行了!')
  return cart.value.reduce((total, item) => {
    const product = products.value.find(p => p.id === item.id)
    return total + (product ? product.price * item.quantity : 0)
  }, 0)
})

// ========== 函数方法 ==========
// 计算购物车总价 - 每次调用都执行
const calculateTotalPrice = () => {
  console.log('函数方法执行了!')
  return cart.value.reduce((total, item) => {
    const product = products.value.find(p => p.id === item.id)
    return total + (product ? product.price * item.quantity : 0)
  }, 0)
}

// 带参数的计算函数
const calculateDiscountedPrice = (discount) => {
  return totalPrice.value * discount
}

// 其他函数方法
const addToCart = (product) => {
  const existingItem = cart.value.find(item => item.id === product.id)
  if (existingItem) {
    existingItem.quantity++
  } else {
    cart.value.push({ ...product, quantity: 1 })
  }
}

const updateProductPrice = () => {
  products.value.forEach(product => {
    product.price += 1
  })
}
</script>

可写计算属性

计算属性不仅可以读取,还可以设置:

<template>
  <div>
    <p>全名: {{ fullName }}</p>
    <input v-model="fullName" placeholder="输入全名">
    <p>姓: {{ firstName }}</p>
    <p>名: {{ lastName }}</p>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

// 可写的计算属性
const fullName = computed({
  // getter - 获取值
  get() {
    return firstName.value + lastName.value
  },
  // setter - 设置值
  set(newValue) {
    // 当输入 "李四" 时,分解为姓和名
    const names = newValue.split('')
    firstName.value = names[0] || ''
    lastName.value = names.slice(1).join('') || ''
  }
})
</script>

实际应用场景

1. 适合用计算属性的场景

<script setup>
import { ref, computed } from 'vue'

const items = ref([
  { name: '苹果', price: 5, quantity: 2 },
  { name: '香蕉', price: 3, quantity: 3 },
  { name: '橙子', price: 4, quantity: 1 }
])

// ✅ 适合:基于现有数据的派生计算
const totalItems = computed(() => items.value.length)
const totalPrice = computed(() => {
  return items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
})
const expensiveItems = computed(() => {
  return items.value.filter(item => item.price > 4)
})
const hasExpensiveItems = computed(() => expensiveItems.value.length > 0)
</script>

2. 适合用函数方法的场景

<script setup>
import { ref } from 'vue'

const message = ref('Hello World')
const users = ref([
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 }
])

// ✅ 适合:需要传参的操作
const greet = (name, greeting = 'Hello') => {
  return `${greeting}, ${name}!`
}

// ✅ 适合:执行副作用的操作
const saveUser = (user) => {
  // 发送请求到服务器
  console.log('保存用户:', user)
}

// ✅ 适合:处理事件
const handleClick = (event) => {
  console.log('按钮被点击了', event)
}

// ✅ 适合:格式化显示(不依赖响应式数据)
const formatDate = (date) => {
  return date.toLocaleDateString()
}
</script>

性能对比示例

<template>
  <div>
    <h2>性能对比测试</h2>
    <p>计数器: {{ count }}</p>
    
    <!-- 计算属性:只计算一次 -->
    <div v-for="n in 5" :key="n">
      计算属性结果: {{ expensiveComputed }}
    </div>
    
    <!-- 函数方法:计算5次 -->
    <div v-for="n in 5" :key="n">
      函数方法结果: {{ expensiveMethod() }}
    </div>
    
    <button @click="increment">增加计数</button>
    <button @click="testPerformance">性能测试</button>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const count = ref(0)

// 模拟耗时计算
const expensiveOperation = (num) => {
  let result = 0
  for (let i = 0; i < 1000000; i++) {
    result += num
  }
  return result
}

// 计算属性:有缓存
const expensiveComputed = computed(() => {
  console.log('计算属性执行')
  return expensiveOperation(count.value)
})

// 函数方法:无缓存
const expensiveMethod = () => {
  console.log('函数方法执行')
  return expensiveOperation(count.value)
}

const increment = () => {
  count.value++
}

const testPerformance = () => {
  console.time('计算属性')
  for (let i = 0; i < 1000; i++) {
    expensiveComputed.value
  }
  console.timeEnd('计算属性')
  
  console.time('函数方法')
  for (let i = 0; i < 1000; i++) {
    expensiveMethod()
  }
  console.timeEnd('函数方法')
}
</script>

总结

特性计算属性函数方法
缓存✅ 有缓存❌ 无缓存
执行时机依赖变化时执行每次调用时执行
传参❌ 不能传参✅ 可以传参
副作用❌ 不应该有副作用✅ 可以有副作用
使用方式像变量一样 {{ fullName }}像函数一样 {{ getFullName() }}
适用场景派生计算、数据转换事件处理、异步操作、复杂逻辑

简单记忆法则

  • 需要缓存、基于响应式数据计算 → 用 computed
  • 需要传参、执行副作用、处理事件 → 用 methods