前言
在 Vue 中,computed 和 watch 的功能 —— 一个负责从数据中推导结果,一个负责监听数据变化后的反应。
核心区别
对比 | computed | watch |
---|---|---|
语法简洁 | ✅ 直接返回派生值 | ❌ 需编写回调函数 |
异步处理 | ❌不支持 | ✅ 支持 |
自动依赖追踪 | ✅ 自动追踪所有依赖 | ❌ 手动指定监听目标 |
返回值 | ✅ 必须返回值 | ❌ 不需要返回值 |
缓存机制 | ✅依赖未变化时,直接返回缓存值 | ❌无缓存,每次变化都触发回调 |
应用场景
- 格式化用户全名
目标:将 firstName 和 lastName 合并为 fullName
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`;
// 依赖自动追踪:当 firstName 或 lastName 变化时自动更新
}
}
自动追踪依赖:无需手动指定依赖项。
缓存机制:若 firstName 和 lastName 没变,多次访问 fullName 不会重复计算。
- 搜索框的联想建议
目标:当输入框内容变化时,延迟 300ms 调用 API 获取联想词。
watch: {
searchKeyword: {
handler(newVal) {
clearTimeout(this.timer); // 防抖处理
this.timer = setTimeout(() => {
this.fetchSuggestions(newVal);
}, 300);
},
immediate: true // 立即触发一次(初始加载)
}
}
支持异步:用于处理如 API 请求等异步逻辑。
手动控制触发时机:通过防抖控制请求频率。
实现原理对比
- computed的内部机制
通过 Watcher 的 dirty 标志实现缓存机制
class Watcher {
constructor(getter) {
this.getter = getter;
this.dirty = true; // 初始标志为脏数据需要重新计算
this.value = null;
// 收集依赖...
}
evaluate() {
this.value = this.getter();
this.dirty = false; // 计算完成后标志为'干净'
}
get() {
if (this.dirty) {
this.evaluate();
}
return this.value;
}
}
触发时机: 当依赖的响应式数据变化时,标记 dirty 为 true(下次访问时重新计算)。
- watch的执行流程
利用 Watcher 监听变化并触发回调
class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm;
this.getter = expOrFn; // 例如 () => this.a
this.cb = cb;
this.value = this.get(); // 初始化时获取初始值
}
update() {
const oldValue = this.value;
this.value = this.get();
this.cb(this.value, oldValue); // 触发回调
}
}
- 监听目标数据的 getter 触发依赖收集。
- 当数据变化时,触发 watcher.update() 执行回调。
总结
何时用computed? | 何时用watch? |
---|---|
根据响应式数据生成派生值 | 响应数据变化触发副作用(如操作DOM、打印日志) |
需要缓存计算结果,避免重复计算 | 需要处理异步或长时间操作(如API请求) |
模板中需要简洁的表达式代替复杂计算逻辑 | 需要监听某个数据变化的初始化即时响应( immediate: true ) |
进阶理解:
- computed:基于响应式系统中对计算结果优化缓存的理念,适用于类似推导公式的场景。
- watch:是观察者模式(Observer Pattern)在前端框架中的经典实现,关注的是数据变化的反应动作,类似“监视器”。
computed = 我需要根据已有数据生成新结果
watch = 我要在数据变化后做某些事情