目录

一、computed 计算属性的工作原理与用途

1. 工作原理

2. 主要用途

二、方法(methods)的工作原理与用途

1. 工作原理

2. 主要用途

三、具体区别(深度对比)

1. 核心差异表

2. 性能对比示例

3. 特殊场景对比

四、面试怎么回答才精彩

🎯 标准回答框架(STAR 法则)

第一层:核心概念(30秒)

第二层:原理深度(1分钟)

第三层:场景选择(体现经验)

第四层:性能优化(加分项)

第五层:Vue 3 新特性(进阶)

🌟 面试金句(记住这些)

📝 完整示例代码


一、computed 计算属性的工作原理与用途

1. 工作原理

响应式依赖追踪机制:

import { ref, computed } from 'vue'

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

// computed 内部会追踪 firstName 和 lastName
const fullName = computed(() => {
  console.log('computed 执行了')
  return firstName.value + lastName.value
})

核心原理:

  • 依赖收集(Dependency Collection): 首次执行时,会收集函数内部访问的所有响应式数据
  • 缓存机制(Caching): 计算结果会被缓存,只有依赖变化时才重新计算
  • 惰性求值(Lazy Evaluation): 只有被访问时才会执行计算
  • 内部标识: 维护 _dirty 标志位,标记是否需要重新计算

底层实现简化版:

class ComputedRefImpl {
  constructor(getter) {
    this._getter = getter
    this._dirty = true  // 脏标记
    this._value = undefined
  }
  
  get value() {
    // 只有 dirty 为 true 时才重新计算
    if (this._dirty) {
      this._value = this._getter()
      this._dirty = false
    }
    return this._value
  }
}

2. 主要用途

  • 衍生状态: 用于根据现有数据计算出新的数据(如:过滤列表、总价计算、格式化字符串)。
  • 性能优化: 处理需要大量计算逻辑的代码,避免在每次渲染时重复计算。

典型应用场景:

// 1. 列表过滤
const filteredList = computed(() => {
  return list.value.filter(item => item.status === 'active')
})

// 2. 总价计算
const totalPrice = computed(() => {
  return cart.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
})

// 3. 表单验证
const isFormValid = computed(() => {
  return username.value.length >= 3 && password.value.length >= 6
})

二、方法(methods)的工作原理与用途

1. 工作原理

  • 普通函数执行: methods 本质上就是普通的 JavaScript 函数。
  • 无缓存: 每当组件重新渲染(Re-render)时,或者在模板中被调用时,methods 总会重新执行函数体。
  • 主动调用: 它不会自动追踪依赖,必须被显式调用(如在模板中 getName() 或在事件处理中调用)。
export default {
  setup() {
    const count = ref(0)
    
    // 普通函数,每次调用都会执行
    const getDouble = () => {
      console.log('method 执行了')
      return count.value * 2
    }
    
    return { count, getDouble }
  }
}

2. 主要用途

  • 事件监听: 处理 DOM 事件(点击、提交等)。
  • 传参逻辑: 当你需要根据传入的参数返回不同的结果时(计算属性不能直接传参,除非返回一个函数)。
  • 非响应式操作: 如发起 API 请求、定时器操作或修改数据状态。

典型应用场景:

// 1. 事件处理
const handleSubmit = () => {
  // 提交表单逻辑
  api.submitForm(formData.value)
}

// 2. 带参数的方法
const formatPrice = (price, currency = '¥') => {
  return `${currency}${price.toFixed(2)}`
}

// 3. 异步操作
const fetchData = async () => {
  loading.value = true
  const res = await api.getData()
  data.value = res
  loading.value = false
}

三、具体区别(深度对比)

1. 核心差异表

维度 Computed 计算属性 Methods 方法
缓存机制 ✅ 有缓存,依赖不变不重新计算 ❌ 无缓存,每次调用都执行
调用方式 像属性访问:fullName 像函数调用:getName()
依赖追踪 ✅ 自动追踪响应式依赖 ❌ 不追踪依赖
传参能力 ❌ 不支持直接传参 ✅ 支持任意参数
执行时机 依赖变化 + 被访问时 被调用时
返回值 ComputedRef 对象 普通函数返回值
副作用 ❌ 不应有副作用(纯函数) ✅ 可以有副作用
性能 高(有缓存) 相对较低(无缓存)

2. 性能对比示例

import { ref, computed } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const other = ref(0)
    
    // Computed:只有 count 变化才重新计算
    const doubleComputed = computed(() => {
      console.log('computed 执行')
      return count.value * 2
    })
    
    // Method:每次渲染都执行
    const doubleMethod = () => {
      console.log('method 执行')
      return count.value * 2
    }
    
    // 改变 other,触发重新渲染
    const changeOther = () => {
      other.value++
      // computed 不会执行(依赖未变)
      // 但模板中的 doubleMethod() 会执行
    }
    
    return { 
      count, 
      other,
      doubleComputed, 
      doubleMethod,
      changeOther 
    }
  }
}

模板中的表现:

<template>
  <div>
    <!-- 访问 computed,有缓存 -->
    <p>Computed: {{ doubleComputed }}</p>
    <p>Computed: {{ doubleComputed }}</p> <!-- 不会重复计算 -->
    
    <!-- 调用 method,无缓存 -->
    <p>Method: {{ doubleMethod() }}</p>
    <p>Method: {{ doubleMethod() }}</p> <!-- 会重复执行 -->
    
    <button @click="changeOther">改变 other</button>
    <!-- 点击后,method 会执行,computed 不会 -->
  </div>
</template>

3. 特殊场景对比

场景1:需要传参

// ❌ Computed 不能直接传参
const getUser = computed((id) => {  // 错误!
  return users.value.find(u => u.id === id)
})

// ✅ 可以返回函数(闭包)
const getUserById = computed(() => {
  return (id) => users.value.find(u => u.id === id)
})
// 使用:getUserById.value(1)

// ✅ Method 直接支持
const getUser = (id) => {
  return users.value.find(u => u.id === id)
}
// 使用:getUser(1)

场景2:副作用操作

// ❌ Computed 不应有副作用
const badComputed = computed(() => {
  console.log('执行了')  // 副作用
  api.logEvent()         // 副作用
  return count.value * 2
})

// ✅ Method 可以有副作用
const goodMethod = () => {
  console.log('执行了')
  api.logEvent()
  return count.value * 2
}

四、面试怎么回答才精彩

🎯 标准回答框架(STAR 法则)

第一层:核心概念(30秒)

"computed 和 methods 最本质的区别是缓存机制。computed 基于响应式依赖进行缓存,只有依赖变化时才重新计算;而 methods 每次调用都会执行,没有缓存。"

第二层:原理深度(1分钟)

"从实现原理来看:

Computed 的核心机制:

  • 内部维护一个 _dirty 标志位和 _value 缓存
  • 首次访问时执行 getter 并收集依赖
  • 依赖变化时只标记 dirty 为 true,不立即计算
  • 下次访问时才真正重新计算(惰性求值)

Methods 的特点:

  • 就是普通的 JavaScript 函数
  • 不参与响应式系统
  • 每次调用都是全新执行"
第三层:场景选择(体现经验)

"在实际开发中,我的选择标准是:

优先使用 Computed:

  • ✅ 数据转换、格式化(如价格、日期)
  • ✅ 列表过滤、排序
  • ✅ 多个状态的组合计算
  • ✅ 表单验证状态

必须使用 Methods:

  • ✅ 事件处理(@click、@submit)
  • ✅ 需要传参的动态查询
  • ✅ 异步操作(API 请求)
  • ✅ 有副作用的操作(修改数据、日志记录)"
第四层:性能优化(加分项)

"从性能角度:

Computed 的优势:

// 假设有 1000 个商品需要过滤
const expensiveList = computed(() => {
  console.log('执行了昂贵的计算')
  return products.value.filter(p => p.price > 1000)
})
// 在模板中多次使用,只计算一次

实际案例:
我曾优化过一个商品列表页面,原本在 methods 中做过滤,每次组件更新都会重新过滤 5000+ 条数据。改用 computed 后,只有筛选条件变化时才重新计算,页面流畅度提升明显。"

第五层:Vue 3 新特性(进阶)

"在 Vue 3 Composition API 中:

  • Computed 返回 ComputedRef<T> 类型,是只读的
  • 可以配置 getter 和 setter,但不推荐滥用
  • 配合 TypeScript 有更好的类型推导
// 可写的 computed
const fullName = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (val) => {
    const [first, last] = val.split(' ')
    firstName.value = first
    lastName.value = last
  }
})
```"

🌟 面试金句(记住这些)

  1. 核心差异:

    "Computed 是以空间换时间(缓存结果),Methods 是以时间换空间(每次计算)"

  2. 使用原则:

    "能用 computed 就用 computed,需要灵活性才用 methods"

  3. 性能优化:

    "在处理大数据量或复杂计算时,合理使用 computed 的缓存特性是 Vue 性能优化的关键手段"

  4. 设计哲学:

    "Computed 体现了声明式编程思想,关注'是什么';Methods 体现了命令式编程,关注'怎么做'"


📝 完整示例代码

import { ref, computed, watch } from 'vue'

export default {
  setup() {
    // 响应式数据
    const products = ref([
      { id: 1, name: 'iPhone', price: 6999, category: 'phone' },
      { id: 2, name: 'iPad', price: 3999, category: 'tablet' },
      { id: 3, name: 'MacBook', price: 9999, category: 'laptop' }
    ])
    const searchText = ref('')
    const selectedCategory = ref('all')
    
    // ✅ Computed:数据过滤(有缓存)
    const filteredProducts = computed(() => {
      console.log('computed 执行了')
      let result = products.value
      
      // 按分类过滤
      if (selectedCategory.value !== 'all') {
        result = result.filter(p => p.category === selectedCategory.value)
      }
      
      // 按搜索文本过滤
      if (searchText.value) {
        result = result.filter(p => 
          p.name.toLowerCase().includes(searchText.value.toLowerCase())
        )
      }
      
      return result
    })
    
    // ✅ Computed:总价计算
    const totalPrice = computed(() => {
      return filteredProducts.value.reduce((sum, p) => sum + p.price, 0)
    })
    
    // ✅ Method:格式化价格(需要传参)
    const formatPrice = (price, showSymbol = true) => {
      const formatted = price.toLocaleString('zh-CN')
      return showSymbol ? `¥${formatted}` : formatted
    }
    
    // ✅ Method:添加商品(有副作用)
    const addProduct = (product) => {
      products.value.push({
        ...product,
        id: Date.now()
      })
      // 副作用:发送统计
      analytics.track('product_added')
    }
    
    // ✅ Method:异步加载
    const loadProducts = async () => {
      const data = await api.getProducts()
      products.value = data
    }
    
    return {
      products,
      searchText,
      selectedCategory,
      filteredProducts,  // computed
      totalPrice,        // computed
      formatPrice,       // method
      addProduct,        // method
      loadProducts       // method
    }
  }
}

关键要点总结:

  • Computed 用于纯计算,无副作用,有缓存
  • Methods 用于业务逻辑,可有副作用,无缓存
  • 性能敏感场景优先 computed
  • 需要灵活性场景使用 methods
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐