vue3中computed实现原理(附带源码)面试重点
主要原理介绍:
✅ computed 实现原理描述:
Vue 3 中的 computed 是基于响应式系统的核心类 ReactiveEffect 与 Proxy 代理机制协同实现的。
computed 的核心实现由 ComputedRefImpl 类构成。该类包含一个关键状态标志 _dirty,用于标识计算属性的值是否已过期(即依赖项发生变化),初始值为 true,表示需要首次求值。
在 ComputedRefImpl 实例化时,会创建一个 ReactiveEffect 实例,并将其赋值给 effect 属性。该 effect 接收用户传入的 getter 函数作为执行逻辑,并传入一个自定义调度函数(scheduler)。这个调度函数的作用是:
- 将
_dirty标志重置为true,标记计算属性为“脏”; - 触发相关依赖的更新通知,使视图或其他依赖者重新收集依赖或更新。
🔍 依赖收集阶段(首次读取):
当首次访问 computed 的值(即调用其 get 方法)时,会执行 effect.run() —— 也就是执行用户的 getter 函数。
在 getter 执行过程中,若访问了其他响应式数据(如 ref 或 reactive 对象),就会触发这些响应式对象的 Proxy 的 get 拦截器。
此时,Vue 的依赖收集核心函数 track(target, key) 被调用,其作用是:
- 判断当前是否有正在执行的
activeEffect(即当前运行的ReactiveEffect实例); - 若有,则建立依赖关系:以
WeakMap结构存储为{ target -> Map{ key -> Set(effect) } }; - 同时将当前
effect添加到其自身的deps数组中,用于后续清理和依赖管理。
这样,computed 的 effect 就成功收集了它所依赖的响应式数据。
🔄 触发更新阶段(依赖变化):
当 computed 所依赖的响应式数据发生变化时,会触发其 Proxy 的 set 拦截器,进而调用 trigger(target, key) 函数。
trigger 会根据 target 和 key 查找所有依赖该数据的 effect 集合,并依次执行它们的调度逻辑。
对于 computed 的 effect,其调度函数会被调用,执行以下操作:
- 设置
_dirty = true,表示计算属性需要重新求值; - 触发通知,告知所有依赖该
computed的副作用(如组件渲染)需要重新执行。
当下一次访问 computed 值时,发现 _dirty === true,便会重新执行 getter 求值,并再次进行依赖收集,完成闭环。
🧠 总结关键点:
| 模块 | 作用 |
|---|---|
ComputedRefImpl | 封装计算属性,管理 _dirty 状态与 effect |
ReactiveEffect | 副作用容器,执行 getter,支持调度函数 |
track | 依赖收集:建立 target -> key -> effect 的映射 |
trigger | 派发更新:通知依赖的 effect 重新执行或调度 |
Proxy | 拦截 get/set,驱动 track 和 trigger |
✅ 一句话概括:
computed通过ReactiveEffect执行getter触发依赖收集(track),并在依赖变更时通过trigger调用调度函数标记_dirty为true,实现惰性求值与响应式更新。
时序图
核心源码
内置方法effect
function effect(fn, options = {}){
const _effect = new ReactiveEffect(fn);
_effect.run();// 默认让响应式的方法执行一次
const runner = _effect.run.bind(_effect)
runner.effect = _effect;
return runner;
}
核心类 ReactiveEffect
let activeEffect;// 激活状态的effect
class ReactiveEffect {
active = true; // 是否属于激活状态
deps = []; // 在重新收集依赖之前清楚之前的
parent = undefined; // 父节点 主要为了解决effect嵌套问题
constructor(publice fn, private scheduler){}
run(){
if(!this.active){
// 未被激活时,直接运行fn 在get方法里进行依赖收集
return this.fn()
}
try {
this.parent = activeEffect;
actiEffect = this;
cleanupEffect(this);
return this.fn();
} finally {
// 释放
activeEffect = this.parent;
this.parent = undefined;
}
}
stop() {
if(this.active){
cleanupEffect(this)
this.active = false;
}
}
}
function cleanupEffect(effect) {
let {deps} = effect;
for(let i = 0; i< deps.length; i++){
deps[i].delete(effect);
}
effect.deps.length = 0;
}
核心方法依赖收集和更新
const targetMap = new WeakMap()
// 该方法会在取值的get方法里调用
function track(target, key){
if(!activeEffect){
// 取值操作没有发生在effect中
return;
}
let depsMap = targetMap.get(target)
if(!depsMap){
targetMap.set(target, (deps = new Map()))
}
let dep = depsMap.get(key);
if(!dep){
depsMap.set(key,(dep = new Set()))
}
trackEffects(dep);
}
function trackEffects(dep){
let shouldTrack = !dep.has(activeEffect)
if(shouldTrack){
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
// 该方法会在set方法里调用
function trigger(target, key, newValue, oldValue){
// weakMap {obj: map{key: set(effect)}}
const depsMap = targetMap.get(target);
if(!depsMap){
return;
}
const dep = depsMap.get(key);
triggerEffects(dep);
}
function triggerEffects(dep) {
if(dep){
const effects = [...dep];
effects.forEach((effect) => {
if(activeEffect != effect){
if(!effect.scheduler){
effect.run();
}else{
effect.scheduler()
}
}
})
}
}
核心类 ComputedRefImpl
clsaa ComputedRefImpl {
dep = undefined;
effect = undefined;
__v_isRef = true;
_dirty = true;
constructor(getter, public setter){
this.effect = new ReactiveEffect(getter, () => {
this._dirty = true;
triggerEffects(this.dep);
})
}
get value(){
if(activeEffect){
trackEffects(this.dep || this.dep = new Set())
}
if(this._dirty){
this._value = this.effect.run();
this._dirty = false
}
return this._value;
}
set value(newValue){
this.setter(newValue)
}
}
computed 函数
const noop = () => {};
function isFunction(obj) {
return typeof obj === 'function';
}
function computed(getterOrOptions){
let onlyGetter = isFunction(getterOrOptions)
let getter;
let setter;
if(onlyGetter){
getter = getterOrOptions;
setter = noop
}else{
getter = getterOrOptions.get;
setter = getterOrOptions.set || noop
}
return new ComputedImpl(getter,setter)
}