computed 与 watch 的区别
这个问题是 Vue.js 面试中的高频考点。作为面试官,我希望听到你对这两个核心特性不仅有概念上的理解,更能结合实际场景,清晰地阐述它们的差异和最佳实践。
核心区别概述
computed (计算属性) 是声明式的,它根据依赖的数据自动计算出一个新的值,并且会缓存计算结果。它更关心的是"结果"。
watch (侦听器) 是命令式的,它用来观察和响应某个数据的变化,并在变化时执行一个"副作用"(side effect),比如执行异步操作或开销较大的操作。它更关心的是"过程"。
详细区别对比
| 特性 | computed (计算属性) | watch (侦听器) |
|---|---|---|
| 功能性质 | 计算、派生。基于一个或多个响应式数据,计算出一个新的值。更像是"是什么"(what)。 | 观察、响应。当某个数据变化时,执行一个动作或副作用。更像是"做什么"(do)。 |
| 缓存机制 | 有缓存 (Cachable)。只要它的依赖数据没有发生变化,多次访问计算属性会立即返回之前缓存的计算结果,而不会重新执行函数。 | 无缓存。只要被侦听的数据发生变化,无论新旧值是否相同(默认情况下),都会执行回调函数。 |
| 异步支持 | 不支持异步。computed 内部必须是同步的 getter 函数,其返回值就是计算结果。在 computed 中进行异步操作是反模式的,也无法得到期望的结果。 | 支持异步 (Asynchronous)。watch 的回调函数中可以执行异步操作,例如发送网络请求、设置定时器等。这是它处理副作用的核心优势。 |
| 调用方式 | 模板中直接使用,像普通属性一样,不加 ()。Vue 会自动处理依赖追踪。 | 在 watch 选项中定义,或者使用 this.$watch API 编程式地创建。 |
| 初始化执行 | 默认不立即执行(懒加载)。只有当第一次访问它时才会计算。 | 默认不立即执行。只有当被侦听的数据第一次改变时才执行。但可以通过 immediate: true 选项让它在组件初始化时立即执行一次。 |
computed 应用场景
computed 的核心在于简化模板和复用逻辑。当一个值依赖于其他几个值,并且这个计算逻辑在模板中多处使用,或者逻辑比较复杂时,就应该使用 computed。
场景1:格式化或组合数据
比如,你有一个 firstName 和 lastName,需要展示完整的 fullName。
data() {
return {
firstName: 'John',
lastName: 'Doe'
}
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}在模板中直接使用 <p></p> 即可,比 <p> </p> 更清晰,且逻辑可复用。
场景2:购物车总价计算
总价依赖于一个商品列表,每个商品有价格和数量。
computed: {
totalPrice() {
return this.cartItems.reduce((total, item) => total + item.price * item.quantity, 0);
}
}只要 cartItems 数组或其中任何一项的 price 或 quantity 变化,totalPrice 就会自动重新计算。因为有缓存,只要购物车数据不变,无论访问多少次 totalPrice,都只会计算一次。
场景3:根据条件筛选列表
从一个完整的列表中,根据一个搜索词 searchText 动态筛选出需要展示的列表。
computed: {
filteredList() {
return this.originalList.filter(item => item.name.includes(this.searchText));
}
}watch 应用场景
watch 的核心在于执行副作用 (side effects)。当一个数据变化,你需要做的不仅仅是计算一个新值,而是要"做一些事情"时,就应该用 watch。
场景1:执行异步操作(如网络请求)
当用户在搜索框中输入问题 question 时,你需要去请求 API 获取答案。这个过程是异步的,必须用 watch。
watch: {
question(newQuestion, oldQuestion) {
// 当 question 变化时,执行这个函数
this.answer = 'Thinking...';
this.fetchAnswer(); // 这是一个异步方法
}
}场景2:操作 DOM 或执行开销大的操作
当某个数值 value 变化时,需要驱动一个复杂的动画库或图表库重新渲染。这些操作可能很耗时,不适合放在 computed 里。
watch: {
value(newValue) {
// 调用第三方库来更新图表
chartLibrary.update(newValue);
}
}场景3:侦听复杂数据类型(对象或数组)
当需要侦听一个对象的属性变化时,需要使用 deep: true 选项。
watch: {
form: {
handler(newValue) {
// 当 form 对象内部的任何属性变化时,执行操作
// 比如:将表单数据存入 localStorage
localStorage.setItem('form-data', JSON.stringify(newValue));
},
deep: true // 深度侦听
}
}选择原则
总的来说,选择用哪个,关键看你的目的:
- 如果你需要一个依赖其他数据计算而来的新数据,并且希望利用缓存来优化性能,就用
computed - 如果你需要在数据变化时执行一些命令式的、有副作用的或者异步的操作,就用
watch
Vue 3 中的变化
在 Vue 3 的 Composition API 中,computed 和 watch 的核心思想是一致的,只是使用形式变成了 computed() 和 watch() / watchEffect() 函数,这让它们在组合式函数中的使用更加灵活。
// Vue 3 Composition API
import { computed, watch, ref } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// computed
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
// watch
watch(firstName, (newValue, oldValue) => {
console.log(`firstName changed from ${oldValue} to ${newValue}`)
})面试回答要点
回答这个问题时,应该从以下几个方面来组织:
- 概念区别:computed 关注结果,watch 关注过程
- 核心差异:缓存机制、异步支持、使用方式
- 应用场景:举出具体的实例来说明何时使用哪个
- 选择原则:能用 computed 就优先用 computed,需要副作用时才用 watch
这样的回答既有理论深度,又有实践场景,逻辑清晰,层次分明,能够充分展示对 Vue 响应式系统的深度理解。