computed的"缓存"特性几乎每个 Vue 开发者都会说,但问到底层是怎么实现的,大部分人就卡了。这篇文章把dirty flag和scheduler这两个核心机制讲清楚,面试时不只是会说"有缓存",而是能说清楚"为什么有缓存"。
第三轮提问(追问):
"你知道
computed有缓存。但我想问的是底层:
- 这个缓存是怎么维护的?依赖变了之后,computed 是立即重新计算,还是等到有人来取值才重新计算?
- Vue3 中有一个叫
scheduler(调度器) 的概念,computed是怎么利用这个机制来实现"懒计算"的?"
参考答案
一、computed 的本质:一个带有特殊 effect 的对象
在 Vue3 源码中,computed(() => price * quantity) 并不是一个简单的函数调用,而是创建了一个 ComputedRefImpl 实例。这个对象上有两个核心属性:
class ComputedRefImpl {
public _value: any // 上一次计算的缓存值
public _dirty: boolean = true // 脏位标记:true 表示"需要重新计算"
// ...
}
true 初始为脏(_dirty = true),代表"我还没算过,需要计算"。
二、关键机制:scheduler 接管了依赖变化的通知
通常情况下,当一个 effect(副作用)依赖的响应式数据变化时,框架会立即重新执行这个 effect。
但 computed 不能这么做——如果模板还没读取 computed 的值,贸然计算一遍是浪费的。
Vue3 的做法是给 computed 内部的 effect 配置一个自定义 scheduler。有了 scheduler,依赖变化时就不再直接执行 effect 了,而是转为执行这个 scheduler 函数。
computed 的 scheduler 只做两件事:
scheduler() {
if (!this._dirty) {
this._dirty = true // 1. 把自己标记为"脏"(需要重新计算)
triggerRefValue(this) // 2. 通知"用了这个 computed 的地方"去更新
}
}
注意:这里没有重新计算,只是打了个标记,然后通知下游。
三、完整的"懒计算"流程
把整个链路串起来:
响应式数据变化(如 price 改变)
↓
触发 computed 内部 effect 的 scheduler
↓
scheduler:把 _dirty 设为 true,通知模板需要更新
↓
Vue 调度器将页面更新任务加入队列
↓
渲染时模板访问 computed.value
↓
触发 computed 的 get value() 拦截器
↓
get value() 里检查:if (this._dirty)
→ 是脏的:执行 getter,拿到新值,存入 _value,_dirty = false
↓
返回 _value 给模板
关键点:getter 函数(实际的计算逻辑)只在真正有人来取值时才执行,这就是"懒计算"。
如果 price 连续改了 100 次,但模板只重新渲染了 1 次,getter 只会被执行 1 次。
四、经典坑:以为"依赖不变就不重算",其实是"脏位没被置 true"
很多同学理解 computed 缓存的方式是:"依赖没变,就用缓存值"——这个描述在表面上没错,但容易造成一个误解。
实际的机制是反向的:
- 初始
_dirty = true,强制第一次取值时计算。 - 依赖变化时,
_dirty被置为true(打脏),下次取值重新计算,计算完_dirty = false。 - 依赖没变,
_dirty保持false,取值时直接返回_value,不执行 getter。
所以缓存的本质,是 _dirty = false 时直接返回 _value,而不是"检测依赖有没有变"。Vue 从不主动去"比较依赖",它只被动地等待依赖告诉它"我变了"。
五、scheduler 的更大意义(升维视角)
scheduler 不只是 computed 在用。Vue3 整个更新调度系统都依赖这个机制——当响应式数据变化时,组件的重新渲染也是通过 scheduler 把更新任务推入一个微任务队列,而不是立即执行。这就是为什么 nextTick 能等到 DOM 更新完之后再执行回调。
能在面试中把 computed 的缓存和 Vue 整体调度机制联系起来说,就是降维打击了。
复盘:三句话记住核心
_dirty = true→ 需要重算;_dirty = false→ 直接返回缓存- 依赖变化时,
scheduler只打"脏"标记 + 通知下游,不立即计算 - 真正的计算推迟到
get value()被访问时——这就是懒计算
下一篇:[[响应式原理04]:
ref/reactive能完全替代 Pinia 吗?什么情况下应该选 Pinia?]