Vue 中 Computed 和 Watch 的深入解析与底层实现

1. 概述

在 项目开发中,我们经常需要处理数据的动态变化,例如:

  • 性能问题 :某个数据需要经过复杂计算,直接在 methods 中调用,每次都会重复执行,导致性能下降
  • 副作用控制 :想要在数据变化时执行异步请求,但又发现某些不必要的调用导致接口请求过多,影响用户体验。
  • 数据依赖管理:某些变量需要依赖其他多个数据源,但逻辑复杂,不容易维护。

在这些场景下,Vue 提供了 Computed(计算属性)Watch(侦听器) 作为响应式解决方案。

然而,很多我们使用时会有这样的问题:

  • 为什么 computed 不会重复计算,而 watch 却会反复触发?
  • computedwatch 看似都能监听数据,实际场景该如何选择?
  • watch 为什么有 deep 选项,而 computed 没有?

本文通过源码解析、依赖收集机制、调度执行、性能优化及最佳实践 等多个角度,深入探讨 computedwatch 的区别,并结合项目实际案例,理解和应用 Vue 的响应式特性。


2. Computed(计算属性) 深入解析

2.1 Computed 的底层实现

在 Vue 2 的源码中,computed 是通过 Watcher 实现的,关键逻辑如下:

  1. 创建 computedWatcher 实例 :当 computed 被定义时,Vue 内部会创建一个 Watcher 实例,并将 lazy 设置为 true(惰性计算)。
  2. 依赖收集 :当 computed 依赖的数据(响应式数据)变化时,该 computedWatcher 会被标记为 dirty,但不会立即重新计算。
  3. 惰性计算 :当 computed 计算属性被访问时,Vue 发现 dirty = true,才会执行计算并缓存结果。
  4. 缓存机制 :如果 dirty = false,则直接返回上次计算的结果,而不会重复计算。

2.2 Vue 2 源码解析(Computed 实现)

在 Vue 2 的 src/core/instance/state.js 文件中,computed 的初始化主要依赖 defineComputed() 函数:

csharp 复制代码
function defineComputed(target, key, userDef) {
  const getter = typeof userDef === 'function' ? userDef : userDef.get;
  sharedPropertyDefinition.get = function computedGetter() {
    const watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate(); // 计算新的值并缓存
      }
      if (Dep.target) {
        watcher.depend(); // 进行依赖收集
      }
      return watcher.value;
    }
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

关键点

  • computed 依赖 Watcher 进行依赖收集 ,但 Watcher 只有在访问 computed 时才会执行计算(惰性计算)。
  • watcher.dirty 作为缓存标志,只有在依赖变化时才会重新计算。

2.3 Computed 的性能优势

  • 避免不必要的重复计算 :由于 computed 具有缓存特性,即使被多次访问,只要依赖未变化,都不会重新计算,从而提升性能。
  • 基于依赖更新,而非主动触发 :相比 watchcomputed仅在依赖变更时更新 ,避免了 watch 可能引起的额外副作用执行。

计算属性缓存 vs 方法调用

xml 复制代码
<template>
  <p>计算属性:{{ reversedMessage }}</p>
  <p>方法调用:{{ reverseMessage() }}</p>
</template>

<script>
export default {
  data() {
    return {
      message: "Hello Vue"
    };
  },
  computed: {
    reversedMessage() {
      console.log("计算属性执行");
      return this.message.split('').reverse().join('');
    }
  },
  methods: {
    reverseMessage() {
      console.log("方法执行");
      return this.message.split('').reverse().join('');
    }
  }
};
</script>

运行结果

  • computed 只会执行一次,后续访问都返回缓存值。
  • methods 每次调用都会重新计算,浪费性能。

3. Watch(侦听器)深入解析

3.1 Watch 的底层实现

watch 在 Vue 2 中同样依赖 Watcher 进行依赖追踪,核心机制包括:

  1. 创建 Watcher 实例watch 监听的数据变化时,Vue 触发 Watcher 实例执行回调函数。
  2. 同步执行 vs 异步执行 :Vue 2 默认使用异步更新策略watch 在组件更新之后执行(nextTick 队列)。
  3. 深度监听(Deep Watching)watch 默认是浅监听 ,如果监听的是对象或数组,需要设置 deep: true 进行递归监听。

源码解析(Vue 2 watch 的实现)

在 Vue 2 的 src/core/observer/watcher.js 文件中,watch 的核心实现如下:

kotlin 复制代码
class Watcher {
  constructor(vm, expOrFn, cb, options) {
    this.vm = vm;
    this.cb = cb;
    this.getter = parsePath(expOrFn);
    this.value = this.get();
  }

  get() {
    Dep.target = this;  // 依赖收集
    let value = this.getter.call(this.vm, this.vm);
    Dep.target = null;
    return value;
  }

  update() {
    const newVal = this.get();
    const oldVal = this.value;
    this.value = newVal;
    this.cb.call(this.vm, newVal, oldVal);
  }
}

关键点

  • 依赖收集: Dep.target = this 用于收集依赖,触发回调。
  • 执行回调: cb.call(this.vm, newVal, oldVal) 当数据变化时执行用户提供的回调函数。

3.2 Watch 的性能挑战

  • 每次变更都会执行回调,即使数据计算结果没有变化。
  • 深度监听可能造成性能问题,因为 Vue 需要遍历整个对象树来检查变化。

Watch 深度监听的性能问题

javascript 复制代码
watch(
  () => user, 
  (newVal, oldVal) => console.log("用户数据变化", newVal),
  { deep: true }
);

如果 user 对象非常庞大,Vue 需要递归遍历所有属性,会增加性能开销。


4. Computed vs Watch 对比

特性 Computed(计算属性) Watch(侦听器)
缓存 是(有缓存) 否(无缓存)
执行时机 访问时触发计算 依赖数据变化后立即执行
适用场景 计算派生数据 监听数据并执行副作用
支持异步
深度监听 是(deep: true)

5. 进阶优化与最佳实践

5.1 结合 Computed 和 Watch 进行优化

在许多实际场景中,我们可以结合 computed watch 进行优化

javascript 复制代码
const fullName = computed(() => `${user.firstName} ${user.lastName}`);

watch(fullName, (newVal) => {
  console.log("用户姓名变化:", newVal);
});

6. 结论

  • computed 适用于计算派生数据,具备缓存特性,提升性能。
  • watch 适用于监听数据变化并执行副作用(如 API 请求、手动 DOM 操作)。
  • computed 应优先使用,只有在 computed 不能满足需求时才使用 watch
  • 避免 watch 监听整个对象,结合 computed 提高效率。

技术沟通交流VX:1010368236

相关推荐
Serene_Dream17 分钟前
JVM 并发 GC - 三色标记
jvm·面试
xjt_090118 分钟前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农29 分钟前
Vue 2.3
前端·javascript·vue.js
夜郎king1 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳1 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵2 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星2 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_2 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝2 小时前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions2 小时前
2026年,微前端终于“死“了
前端·状态模式