Hi我是前端人类学Vue3 的响应式系统是一次彻底的重构——从 Object.defineProperty 换装为 Proxy不仅解决了 Vue2 中新增属性无法响应、数组索引变更无法感知等痛点更在性能和组织上做了全新设计。本文将从源码层面拆解其核心机制理解ref与reactive的本质差异并走通从依赖收集到派发更新的完整流程。文章目录一、为什么是 Proxy—— Vue2 的痛点与 Vue3 的破局二、reactive 源码拆解Proxy 如何代理对象2.1 类型校验与缓存2.2 可观察性校验2.3 创建 Proxy2.4 baseHandlers 的核心get 与 set三、ref 的本质为原始值套上响应式外壳3.1 RefImpl 类3.2 模板自动解包3.3 ref vs reactive如何选择四、依赖收集完整流程track 与 trigger 的协作4.1 activeEffect 与 effect 栈4.2 track依赖收集4.3 trigger派发更新4.4 Vue3.5 的响应式优化双向链表一、为什么是 Proxy—— Vue2 的痛点与 Vue3 的破局在 Vue2 中响应式数据通过Object.defineProperty对对象的已有属性逐个进行 getter/setter 劫持。这带来了两个核心限制无法检测属性的新增和删除data初始化时不存在的属性后续赋值无法触发响应式更新必须使用Vue.set或this.$set来补救。数组变更的局限性直接通过索引修改数组项arr[0] x无法被检测Vue2 只能通过重写push、pop等 7 个数组方法来实现响应式。Proxy则完全不同——它代理的是整个对象而非某个具体属性。任何对对象的操作读取、写入、删除、in操作等都会先经过Proxy的拦截器。constdatanewProxy(raw,{get(target,key){/* 拦截任意属性的读取 */},set(target,key,value){/* 拦截任意属性的写入 */}})这意味着无论属性是否预先存在只要对代理对象进行操作拦截器都会生效。新增属性state.newProp hi能自动触发更新数组索引修改arr[0] 10同样能被捕获。这是 Vue3 响应式系统最底层的进化。二、reactive 源码拆解Proxy 如何代理对象Vue3 中reactiveAPI 的核心逻辑在createReactiveObject函数中。我们拆解其关键步骤2.1 类型校验与缓存functioncreateReactiveObject(target,isReadonly,baseHandlers,collectionHandlers,proxyMap){// 非对象直接返回if(!isObject(target))returntarget;// 已经是代理对象直接返回if(target[ReactiveFlags.RAW]!(isReadonlytarget[ReactiveFlags.IS_REACTIVE])){returntarget;}// 从缓存中取避免重复代理constexistingProxyproxyMap.get(target);if(existingProxy)returnexistingProxy;// ...}proxyMap是一个WeakMap以原始对象为 key代理对象为 value 进行缓存。这样同一个对象多次调用reactive会返回同一个代理实例。2.2 可观察性校验getTargetType会检查目标对象的类型是否在白名单中Object、Array、Map、Set、WeakMap、WeakSet可以被代理否则返回TargetType.INVALID直接跳过。2.3 创建 Proxy对于普通对象和数组使用baseHandlers即mutableHandlers对于Map/Set等集合类型使用collectionHandlers。constproxynewProxy(target,targetTypeTargetType.COLLECTION?collectionHandlers:baseHandlers);proxyMap.set(target,proxy);returnproxy;2.4 baseHandlers 的核心get 与 setmutableHandlers是代理行为的核心包含了get、set、deleteProperty、has、ownKeys等拦截器。get拦截器负责依赖收集track和深度代理functionget(target,key,receiver){// 内置标志位处理if(keyReactiveFlags.IS_REACTIVE)returntrue;constresReflect.get(target,key,receiver);// 依赖收集track(target,key);// 深度响应式取值时如果结果是对象递归调用 reactivereturnisObject(res)?reactive(res):res;}注意这里的惰性代理嵌套对象只在被访问到时才被代理而非初始化时递归遍历所有层级这带来了性能优势。set拦截器负责触发更新triggerfunctionset(target,key,value,receiver){constoldValuetarget[key];constresultReflect.set(target,key,value,receiver);if(oldValue!value){trigger(target,key);// 值变化时触发更新}returnresult;}deleteProperty同样会调用trigger确保删除属性也能触发响应。三、ref 的本质为原始值套上响应式外壳reactive只能代理对象对于number、string、boolean等原始值无能为力。ref正是为此而生——它通过一个包装对象让原始值也具备响应性。3.1 RefImpl 类ref的底层是一个RefImpl类核心结构非常简洁classRefImpl{public__v_isReftrue;private_value:any;publicdep:SetReactiveEffectnewSet();constructor(value){// 如果传入的是对象内部用 reactive 转为响应式this._valueisObject(value)?reactive(value):value;}getvalue(){trackRefValue(this);// 依赖收集returnthis._value;}setvalue(newVal){if(newVal!this._value){this._valueisObject(newVal)?reactive(newVal):newVal;triggerRefValue(this);// 触发更新}}}ref返回的就是RefImpl的实例通过get value()和set value()拦截对.value的读写从而实现响应式。ref的底层并不直接等于reactive——只有当传入的值是对象时才会将对象交给reactive处理对于原始值ref自己维护依赖收集和派发更新。3.2 模板自动解包在模板中直接使用ref时不需要写.value这是因为 Vue 在编译阶段对模板中的ref做了自动解包处理。另外当ref作为reactive对象的属性被访问时也会自动解包constcountref(0);conststatereactive({count});console.log(state.count);// 0自动解包不需要 .value3.3 ref vs reactive如何选择特性refreactive适用类型任意类型原始值 对象仅对象含数组、集合访问方式.value直接访问属性底层实现RefImpl类 .value拦截Proxy代理整个对象解构响应性解构失去响应性解构失去响应性模板中使用自动解包无需.value直接使用使用建议对于基础类型和不确定类型的值优先用ref对于结构清晰的对象状态用reactive更自然。如果实在不确定ref是更通用的选择。四、依赖收集完整流程track 与 trigger 的协作依赖收集是响应式系统的心脏。Vue3 的依赖管理以targetMap为核心数据结构它是一个WeakMap结构为targetMap: WeakMaptarget, Mapkey, SetReactiveEffect即目标对象 → (属性名 → 副作用函数集合)。4.1 activeEffect 与 effect 栈在依赖收集期间需要一个全局变量记录当前正在执行的副作用函数——这就是activeEffect。effect函数watchEffect、render等底层都依赖它在执行前会把自身设置为activeEffect执行完毕后重置。classReactiveEffect{run(){try{effectStack.push((activeEffectthis));returnthis.fn();// 执行用户函数触发 get 拦截}finally{effectStack.pop();activeEffecteffectStack[effectStack.length-1];}}}effectStack用于处理嵌套 effect场景如组件嵌套确保依赖始终收集到正确的副作用函数上。4.2 track依赖收集当访问响应式数据的属性时触发get拦截器内部调用track函数functiontrack(target,key){if(!activeEffect)return;// 没有活跃的 effect跳过letdepsMaptargetMap.get(target);if(!depsMap)targetMap.set(target,(depsMapnewMap()));letdepdepsMap.get(key);if(!dep)depsMap.set(key,(depnewSet()));dep.add(activeEffect);}track做的事情很纯粹建立 target → key → effect 的映射关系。activeEffect作为当前正在运行的副作用函数被收入对应 key 的依赖集合中。4.3 trigger派发更新当修改响应式数据的属性时触发set拦截器内部调用trigger函数functiontrigger(target,key){constdepsMaptargetMap.get(target);if(!depsMap)return;constdepdepsMap.get(key);if(dep){dep.forEach(effecteffect.run());// 遍历执行所有副作用函数}}trigger从targetMap中取出该属性对应的所有依赖副作用函数逐个执行完成更新。4.4 Vue3.5 的响应式优化双向链表Vue3.5 对响应式系统做了重大重构引入了**双向链表Link**连接 Dep依赖和 Sub订阅者替代了之前dep和effect的直接关联。这一重构带来了56% 的性能提升。在新的模型中Dep响应式变量和Sub副作用函数之间不再直接引用。Link作为桥梁同时持有dep和sub引用并通过nextDep/prevDep、nextSub/prevSub形成 X 轴和 Y 轴的双向链表结构。依赖触发时从 Dep 指向的链表尾部开始沿 Y 轴依次访问所有 Sub执行对应的副作用函数。这种结构使得依赖的增删和遍历更加高效。Vue3 的响应式系统可以概括为三层代理层Proxy拦截对象操作reactive负责对象响应式RefImpl为原始值提供.value包装。依赖管理层targetMap以target → key → Set(effect)的层级存储依赖track收集trigger派发。调度执行层activeEffect和effectStack管理当前活跃的副作用函数Vue3.5 进一步用双向链表优化了依赖的组织与遍历。理解这一套机制既能帮你在日常开发中更自信地选择ref还是reactive也能让你在遇到响应式失效的怪问题时快速定位根因——而不再需要靠加个setTimeout试试来碰运气。