计算属性原理拆解
更新: 8/5/2026字数: 0 字 时长: 0 分钟
一、 Computed(计算属性)核心原理拆解
Vue 中的 computed 并不是简单的“语法糖”,而是一个带缓存的响应式观察者。
它的核心机制可以总结为两点:
- 延迟求值(Lazy Evaluation): 只有当依赖的响应式数据改变,或者被外部模板/代码显式读取时,它才会重新计算。
- 缓存机制(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.defineProperty | ES6 Proxy + Reflect |
| 底层类 / 数据结构 | Computed Watcher(基于 Watcher 类) | ComputedRefImpl(基于响应式 Effect) |
| 缓存控制标识 | watcher.dirty | computedRef.effect._dirty (或 dirty Getter) |
| 双向依赖收集 | 通过 Watcher 内部手动管理 deps 数组与 Dep 相互记录 | 通过全局 track / trigger 与 effect 关联收集 |
| 链式收集(嵌套依赖) | 需要手动调用 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),底层都是它的实例。
针对 computed,Watcher 内部设计了两个核心机制:
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 步运转流程
- 初始化: 为计算属性实例化一个
lazy: true的 Watcher。此时dirty = true,不执行计算。 - 首次读取: 检查到
dirty === true调用evaluate()执行计算 收集内部依赖 存入this.value并将dirty置为false返回结果。 - 再次读取(数据未变): 检查到
dirty === false跳过计算,直接返回缓存的this.value。 - 依赖变更: 内部数据修改触发
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 的优化点:
- 更轻量级的内存占用: Vue 3 的
ComputedRefImpl比 Vue 2 的Watcher结构轻量得多,没有冗余的实例属性与数组去重判断,性能开销更低。 - 更贴合组合式 API(Composition API): Vue 3 的
computed()可以在任何地方独立使用(不局限于组件选项内),它返回一个独立的ref对象(ComputedRef),天生支持模块化拆分与灵活组合。 - 响应式系统解耦: 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对象,具备更优的内存占用、更高的执行效率以及更自由的组合能力。