Skip to content
 

计算属性原理拆解

更新: 8/5/2026字数: 0 字 时长: 0 分钟

一、 Computed(计算属性)核心原理拆解

Vue 中的 computed 并不是简单的“语法糖”,而是一个带缓存的响应式观察者

它的核心机制可以总结为两点:

  1. 延迟求值(Lazy Evaluation): 只有当依赖的响应式数据改变,或者被外部模板/代码显式读取时,它才会重新计算。
  2. 缓存机制(Cache): 如果依赖的数据没有发生变化,无论你访问 computed 多少次,它都会直接返回上一次计算好的结果,而不会重复执行内部函数。

为了实现缓存与延迟求值,Vue 内部为每个 computed 维护了一个重要的脏标记位(dirty 标记)

  • dirty = true 时,说明依赖的数据变了,下一次读取 computed 时需要重新计算,计算完后将 dirty 设为 false
  • dirty = false 时,说明依赖的数据没变,下一次读取 computed直接返回缓存结果

二、 Vue 2 vs Vue 3 计算属性实现机制对比

Vue 2 和 Vue 3 的 computed基本思想(dirty 标记 + 依赖收集)上是一致的,但在底层实现架构依赖追踪方式上有着本质的区别。

1. 核心差异对比表

维度Vue 2 实现方式Vue 3 实现方式
底层数据劫持Object.definePropertyES6 Proxy + Reflect
底层类 / 数据结构Computed Watcher(基于 Watcher 类)ComputedRefImpl(基于响应式 Effect
缓存控制标识watcher.dirtycomputedRef.effect._dirty (或 dirty Getter)
双向依赖收集通过 Watcher 内部手动管理 deps 数组与 Dep 相互记录通过全局 track / triggereffect 关联收集
链式收集(嵌套依赖)需要手动调用 watcher.depend() 显式将外层 Watcher 收集进依赖 Dep通过 effect 的链式触发与 trackRefValue 自动传递依赖

2. Vue 2 的实现细节:基于 Watcher 的懒计算与缓存

在 Vue 2 中,computed 本质上就是一个 Computed Watcher

2.1 什么是 Watcher?

Watcher 是 Vue 2 中专门用于“订阅数据变化并执行任务”的核心类。无论是组件渲染(Render Watcher)、自定义监听(User Watcher),还是计算属性(Computed Watcher),底层都是它的实例。

针对 computedWatcher 内部设计了两个核心机制:

  • lazy = true 懒加载标记,创建时不立即执行计算。
  • dirty 脏标记: 缓存控制开关。为 true 表示需要重新计算,为 false 表示直接使用缓存。
javascript
class Watcher {
  constructor(vm, getter, options = {}) {
    this.getter = getter;       // 用户写的 computed 函数
    this.lazy = !!options.lazy; // computed 传 true
    this.dirty = this.lazy;     // 初始为 true
    this.value = undefined;     // 缓存值
  }

  // 重新计算并刷新缓存
  evaluate() {
    Dep.target = this;
    this.value = this.getter.call(this.vm); // 执行计算,触发内部依赖的 getter 进行收集
    Dep.target = null;
    this.dirty = false; // 关掉脏标记,完成缓存
  }

  // 依赖的数据改变时触发
  update() {
    if (this.lazy) {
      this.dirty = true; // 仅标记为脏数据,不立即计算
    } else {
      queueWatcher(this); // 普通 Watcher 走异步更新队列
    }
  }
}

2.2 computed 的 4 步运转流程

  1. 初始化: 为计算属性实例化一个 lazy: true 的 Watcher。此时 dirty = true不执行计算
  2. 首次读取: 检查到 dirty === true 调用 evaluate() 执行计算 收集内部依赖 存入 this.value 并将 dirty 置为 false 返回结果。
  3. 再次读取(数据未变): 检查到 dirty === false 跳过计算,直接返回缓存的 this.value
  4. 依赖变更: 内部数据修改触发 setter 调用 watcher.update()dirty 重新置为 true 触发外层组件重新渲染 下次读取时重新走第 2 步计算。

3. Vue 3 的实现细节(基于 Effect 与 Ref)

Vue 3 摒弃了 Vue 2 重型的 Watcher 类,转向基于纯粹函数式思想的 Effect 架构,计算属性被封装为一个特殊的 Ref 实例(ComputedRefImpl)。

Vue 3 ComputedRefImpl 伪代码简化实现:

javascript
import { ReactiveEffect, trackRefValue, triggerRefValue } from '@vue/reactivity';

class ComputedRefImpl {
  private _value;
  private _dirty = true; // 缓存标记
  public readonly effect; // 内部封装的计算 effect

  constructor(getter) {
    // 实例化一个响应式 Effect
    this.effect = new ReactiveEffect(
      getter,
      // scheduler(调度器):当计算属性依赖的数据发生变化时触发
      () => {
        if (!this._dirty) {
          this._dirty = true; // 依赖变了,标记为“脏数据”
          triggerRefValue(this); // 通知外层依赖(如渲染 effect)重新运行
        }
      }
    );
  }

  get value() {
    // 1. 收集读取该 computed 的外层依赖(比如组件 Render Effect)
    trackRefValue(this);

    // 2. 如果是脏数据,重新计算并缓存结果
    if (this._dirty) {
      this._dirty = false;
      this._value = this.effect.run(); // 执行真正的计算 getters
    }

    // 3. 返回缓存的计算值
    return this._value;
  }
}

Vue 3 的优化点:

  1. 更轻量级的内存占用: Vue 3 的 ComputedRefImpl 比 Vue 2 的 Watcher 结构轻量得多,没有冗余的实例属性与数组去重判断,性能开销更低。
  2. 更贴合组合式 API(Composition API): Vue 3 的 computed() 可以在任何地方独立使用(不局限于组件选项内),它返回一个独立的 ref 对象(ComputedRef),天生支持模块化拆分与灵活组合。
  3. 响应式系统解耦: Vue 3 将依赖收集(track)与派发更新(trigger)抽离为底层的通用模块,computed 只是作为一个中间桥梁(既是订阅者又是被订阅者),代码逻辑比 Vue 2 的多重嵌套和手动双向收集清晰许多。

三、 总结

  • 相同点: 无论 Vue 2 还是 Vue 3,computed 的核心都是懒计算(Lazy)依赖驱动的缓存机制(基于 dirty 标记)
  • 不同点:
  • Vue 2 依赖 Object.defineProperty 与类对象 Watcher / Dep 配合,依赖关系通过复杂的双向引用手动维护;
  • Vue 3 借助 Proxy 与函数式 Effect 架构,将 computed 实现为一个轻量级的 ComputedRefImpl 对象,具备更优的内存占用、更高的执行效率以及更自由的组合能力。