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

相关推荐
前端付杰13 分钟前
从Vue源码解锁位运算符:提升代码效率的秘诀
前端·javascript·vue.js
然后就去远行吧14 分钟前
小程序 wxml 语法 —— 37 setData() - 修改对象类型数据
android·前端·小程序
用户32035783600215 分钟前
程序员鸡翅-Java微服务从0到1带你做社区项目实战
javascript
用户32035783600216 分钟前
高薪运维必备Prometheus监控系统企业级实战(已完结)
前端
一只爱打拳的程序猿20 分钟前
【SpringBoot】实现登录功能
javascript·css·spring boot·mybatis·html5
黄天才丶22 分钟前
高级前端篇-脚手架开发
前端
乐闻x35 分钟前
React 如何实现组件懒加载以及懒加载的底层机制
前端·react.js·性能优化·前端框架
小鱼冻干37 分钟前
http模块
前端·node.js
悬炫37 分钟前
闭包、作用域与作用域链:概念与应用
前端·javascript
jiaHang39 分钟前
小程序中通过IntersectionObserver实现曝光统计
前端·微信小程序