computed
computed(计算属性)的核心思想是:基于响应式依赖进行缓存的计算值,只有当依赖发生变化时才会重新计算。
Vue2 中 computed 的实现原理
Vue2 的 computed 本质上是一个特殊的 Watcher,具有 lazy(惰性求值) 和 缓存 特性。
js
// 简化版 computed 实现
function initComputed(vm, computed) {
const watchers = vm._computedWatchers = Object.create(null);
for (const key in computed) {
const userDef = computed[key];
const getter = typeof userDef === 'function'
? userDef
: userDef.get;
// 为每个计算属性创建一个 computed watcher
watchers[key] = new Watcher(
vm,
getter || (() => {}),
() => {}, // 回调函数为空,因为 computed 更新由渲染 watcher 触发
{
lazy: true, // 关键:标记为 computed watcher
computed: true
}
);
// 将计算属性定义到 vm 实例上
defineComputed(vm, key, userDef);
}
}
function defineComputed(target, key, userDef) {
// 处理 setter(如果提供)
const setter = userDef.set || (() => {
console.warn(`Computed property "${key}" was assigned to but has no setter.`);
});
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get: createComputedGetter(key),
set: setter
});
}
// 创建计算属性的 getter 函数
function createComputedGetter(key) {
return function computedGetter() {
const watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
// 关键:只有 dirty 为 true 时才重新计算
if (watcher.dirty) {
watcher.evaluate(); // 重新计算值,并标记 dirty 为 false
}
// 依赖收集:让当前渲染 watcher 收集到这个 computed watcher 作为依赖
if (Dep.target) {
watcher.depend();
}
return watcher.value;
}
};
}
// 扩展 Watcher 类以支持 computed
class Watcher {
constructor(vm, expOrFn, cb, options = {}) {
this.vm = vm;
// computed watcher 特有的属性
this.lazy = !!options.lazy;
this.dirty = this.lazy; // 对于 computed watcher,初始时 dirty 为 true
this.computed = !!options.computed;
if (this.computed) {
this.dep = new Dep(); // computed watcher 拥有自己的 dep,用于收集依赖它的渲染 watcher
}
this.getter = typeof expOrFn === 'function' ? expOrFn : parsePath(expOrFn);
this.cb = cb;
// 如果不是 lazy watcher,则立即获取值
this.value = this.lazy ? undefined : this.get();
}
get() {
pushTarget(this); // 将当前 watcher 设置为 Dep.target
let value;
try {
value = this.getter.call(this.vm, this.vm);
} finally {
popTarget(); // 恢复之前的 watcher
}
return value;
}
evaluate() {
// 只有 computed watcher 会调用此方法
this.value = this.get();
this.dirty = false; // 标记为已计算
}
depend() {
// computed watcher 的依赖收集
if (this.dep && Dep.target) {
this.dep.depend(); // 让当前 watcher(通常是渲染 watcher)收集这个 computed watcher
}
}
update() {
if (this.lazy) {
// computed watcher 收到更新通知时,只标记 dirty,不立即重新计算
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this); // 推入异步更新队列
}
}
run() {
const value = this.get();
if (value !== this.value || this.deep) {
const oldValue = this.value;
this.value = value;
this.cb.call(this.vm, value, oldValue);
}
}
}
Vue2 computed 的工作流程
js
// 示例代码
new Vue({
data() {
return { count: 1, multiplier: 2 };
},
computed: {
total() {
console.log('计算 total');
return this.count * this.multiplier;
}
},
template: '<div>{{ total }}</div>'
});
初始化阶段:
- 为
total创建 computed watcher ,设置lazy: true, dirty: true - 不立即计算,等待首次访问
首次渲染阶段:
-
渲染函数读取
this.total -
触发
computedGetter,发现watcher.dirty = true -
调用
watcher.evaluate():- 执行
this.get(),触发total函数 - 读取
this.count和this.multiplier - 将 computed watcher 收集为
count和multiplier的依赖 - 计算结果
2,缓存到watcher.value - 设置
dirty = false
- 执行
-
渲染 watcher 通过
watcher.depend()收集 computed watcher 作为依赖 -
返回缓存值
2
依赖更新阶段:
this.count = 3触发setter- 通知所有依赖的 watcher(包括 computed watcher)
- computed watcher 的
update()被调用,设置dirty = true - 但此时不会重新计算,只是标记为脏
重新渲染阶段:
- 渲染函数再次读取
this.total - 触发
computedGetter,发现watcher.dirty = true - 重新计算,返回新值
6 - 设置
dirty = false
Vue3 中 computed 的实现原理
Vue3 的 computed 基于响应式系统的 effect 和 ref 实现。
js
// 简化版 computed 实现
class ComputedRefImpl {
constructor(getter, setter) {
this._setter = setter;
this._value = undefined;
this._dirty = true; // 缓存标记
this._dep = new Set(); // 存储依赖此 computed 的 effect
this._effect = null;
// 创建响应式 effect
this._effect = new ReactiveEffect(
() => {
// 执行 getter,收集依赖
return getter();
},
() => {
// 调度器:依赖变化时被调用
if (!this._dirty) {
this._dirty = true;
// 触发依赖此 computed 的所有 effect
triggerRefValue(this);
}
}
);
this._effect.computed = this;
}
get value() {
// 收集依赖
trackRefValue(this);
// 只有 dirty 时才重新计算
if (this._dirty) {
this._dirty = false;
this._value = this._effect.run();
}
return this._value;
}
set value(newValue) {
this._setter && this._setter(newValue);
}
}
// 创建 computed
function computed(getterOrOptions) {
let getter;
let setter;
if (typeof getterOrOptions === 'function') {
getter = getterOrOptions;
setter = () => {
console.warn('Write operation failed: computed value is readonly');
};
} else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}
return new ComputedRefImpl(getter, setter);
}
// 依赖收集和触发
function trackRefValue(ref) {
if (activeEffect) {
ref._dep.add(activeEffect);
activeEffect.deps.push(ref._dep);
}
}
function triggerRefValue(ref) {
const effects = [...ref._dep];
for (const effect of effects) {
if (effect !== activeEffect) {
if (effect.scheduler) {
effect.scheduler();
} else {
effect.run();
}
}
}
}
Vue3 computed 的工作流程
js
// 示例代码
import { ref, computed } from 'vue';
const count = ref(1);
const multiplier = ref(2);
const total = computed(() => {
console.log('计算 total');
return count.value * multiplier.value;
});
// 创建 effect 依赖 total
const stop = effect(() => {
console.log('total 值:', total.value);
});
初始化阶段:
- 创建
ComputedRefImpl实例 - 创建
ReactiveEffect,传入 getter 和调度器 - 标记
_dirty = true
首次访问阶段:
-
effect执行,读取total.value -
触发
get value():trackRefValue(this):将当前 effect 收集到this._dep- 由于
_dirty = true,执行this._effect.run() - 执行 getter,读取
count.value和multiplier.value - computed 的 effect 被收集为
count和multiplier的依赖 - 计算结果
2,缓存到this._value - 设置
_dirty = false
-
返回
2
依赖更新阶段:
-
count.value = 3触发响应式更新 -
通知所有依赖的 effect(包括 computed 的 effect)
-
computed effect 的调度器被调用:
- 设置
_dirty = true triggerRefValue(this):触发依赖此 computed 的所有 effect
- 设置
重新访问阶段:
- 依赖 computed 的 effect 重新执行
- 再次读取
total.value - 由于
_dirty = true,重新计算,返回6
总结
Vue的计算属性是一个带缓存的副作用函数,它惰性求值并在依赖未变化时直接返回缓存结果。
这个实现本质可以拆解为三个核心机制:
- 缓存机制 :内部维护
dirty标志和值缓存,依赖未变时直接返回旧值 - 惰性求值:只有被访问时才执行计算函数
- 依赖追踪:自动收集响应式依赖,依赖变化时标记缓存失效