ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Vue 3 核心响应式与渲染原理全景拆解

2026/8/5 19:08:24 拓冰建站 浏览量
Vue 3 核心响应式与渲染原理全景拆解

文章目录

  • Vue 3 核心响应式与渲染原理全景拆解
    • 1. 响应式基石:`Proxy` 与 `Reflect`
      • 为什么 `Proxy` 优于 `Object.defineProperty`?
      • 关键代码逻辑(简化版)
    • 2. 依赖收集核心:`Effect` 与全景 `targetMap` 映射表
      • 依赖关系映射结构(`targetMap`)
    • 3. `ref` 与 `reactive` 原理拆解
      • `reactive`
      • `ref`
    • 4. 渲染与 Diff 优化:编译时与运行时的高效协同
      • ① Block Tree 与动态节点标记(`PatchFlag`)
      • ② 最长递增子序列算法(Fast Diff)
    • 5. 组件级渲染机制与异步更新队列
      • 异步队列(`queueJob`)与 `nextTick`
    • 6. Vue 2 vs Vue 3 全景架构对比总结

Vue 3 核心响应式与渲染原理全景拆解

如果说 Vue 2 的响应式是一套设计精巧的“面向对象类系统(Watcher / Dep)”,那么 Vue 3 则彻底演变为一套基于 ES6+ 现代特性的“高并发、轻量化、函数式系统(Proxy / Effect)”

1. 响应式基石:ProxyReflect

Vue 3 抛弃了Object.defineProperty,转而使用 ES6 的Proxy(代理)配合Reflect(反射)实现数据的响应式劫持。

为什么Proxy优于Object.defineProperty

维度Vue 2 (Object.defineProperty)Vue 3 (Proxy)
劫持对象属性级别(需要递归遍历每一个 key)对象级别(直接代理整个对象)
新增/删除属性无法侦测,需借助this.$set/$delete天然支持set/deleteProperty拦截)
数组支持依赖拦截重写 7 个变异方法,无法感知下标修改天然支持(正常拦截数组索引修改与length变化)
初始化性能递归初始化所有属性,层级越深越慢按需惰性代理(只有读取到深层对象时才递归创建reactive

关键代码逻辑(简化版)

functionreactive(target){if(typeoftarget!=='object'||target===null)returntarget;returnnewProxy(target,{get(target,key,receiver){// 1. 依赖收集 (track)track(target,key);constres=Reflect.get(target,key,receiver);// 2. 惰性响应式:取值时发现是对象,才进行深层代理returntypeofres==='object'&&res!==null?reactive(res):res;},set(target,key,value,receiver){constoldValue=target[key];constresult=Reflect.set(target,key,value,receiver);// 3. 值发生变化时派发更新 (trigger)if(hasChanged(value,oldValue)){trigger(target,key);}returnresult;}});}

2. 依赖收集核心:Effect与全景targetMap映射表

Vue 3 废弃了 Vue 2 的Dep类,引入了全局统一的双重 WeakMap / Map / Set 依赖数据结构targetMap)和底层执行单元Effect(作用/副作用)

依赖关系映射结构(targetMap

在内存中,响应式依赖通过如下关系精确绑定:

targetMap (WeakMap) └── target 对象 (Key) └── depsMap (Map) └── key 属性 (Key) └── dep (Set: 存放所有依赖该属性的 ReactiveEffect)
// 全局依赖树consttargetMap=newWeakMap();letactiveEffect=null;// 当前正在运行的 Effect// 1. 依赖收集:trackfunctiontrack(target,key){if(!activeEffect)return;letdepsMap=targetMap.get(target);if(!depsMap){targetMap.set(target,(depsMap=newMap()));}letdep=depsMap.get(key);if(!dep){depsMap.set(key,(dep=newSet()));}// 将当前副作用添加进 Set 集合(自动去重)dep.add(activeEffect);}// 2. 派发更新:triggerfunctiontrigger(target,key){constdepsMap=targetMap.get(target);if(!depsMap)return;constdep=depsMap.get(key);if(dep){// 拷贝一份防止无限循环,依次执行副作用consteffectsToRun=newSet(dep);effectsToRun.forEach(effect=>{if(effect.scheduler){effect.scheduler();// 如果有调度器(如组件渲染队列、computed 脏标记),优先走调度器}else{effect.run();// 否则直接运行}});}}

3.refreactive原理拆解

Vue 3 提供了两种主要的响应式声明 API:reactiveref

reactive

  • 作用对象:仅接收复杂数据类型(Object / Array / Map / Set)
  • 实现原理:底层基于 ES6Proxy对对象进行全方位包装。

ref

  • 作用对象:既可以接收原始值(String / Number / Boolean),也可以接收对象。
  • 实现原理:
  • 由于 ES6Proxy无法直接代理原始值,Vue 3 包装了一个RefImpl
  • 利用getter/setter(属性访问器)拦截.value的读写动作;
  • 如果ref传入的是对象,底层会自动调用reactive转为Proxy
classRefImpl{constructor(value){this._rawValue=value;// 如果是对象则调用 reactive 代理,否则返回原值this._value=isObject(value)?reactive(value):value;this.__v_isRef=true;// 属性标记,用于自动解包}getvalue(){trackRefValue(this);// 手动触发 ref 的依赖收集returnthis._value;}setvalue(newVal){if(hasChanged(newVal,this._rawValue)){this._rawValue=newVal;this._value=isObject(newVal)?reactive(newVal):newVal;triggerRefValue(this);// 触发更新}}}

4. 渲染与 Diff 优化:编译时与运行时的高效协同

Vue 3 不仅重构了响应式,还大幅优化了编译(Compiler)与运行时(Runtime)的配合,实现了极其高效的 DOM Diff。

① Block Tree 与动态节点标记(PatchFlag

Vue 2 在数据更新时,需要逐层对整棵虚拟 DOM 树(VNode)进行递归对比。
Vue 3 引入了静态提升(Static Hoisting)动态节点标记(PatchFlag)

  • 动态节点(Dynamic Nodes):编译阶段分析出哪些节点带数据绑定(如:class="cls"\{\{ text \}\}),并赋予对应的位掩码PatchFlag(如1代表仅动态文本,2代表仅动态 class)。
  • Block 容器:根节点将所有动态子节点收集到一个一维数组dynamicChildren中。
  • 更新阶段:Diff 过程跳过所有静态节点,直接遍历dynamicChildren数组,并且只对比PatchFlag指定的属性(如只比对 class,不比对属性与文本)。

② 最长递增子序列算法(Fast Diff)

在列表 Diff 阶段(如v-for顺序大洗牌),Vue 2 使用双端对比算法(Four-pointer Diff),而 Vue 3 采用了效率更高的Fast Diff 算法

  1. 预处理同步头尾:先分别从头、从尾比对相同节点,直接复用并挂载。
  2. 构建映射表:对中间剩余的乱序子节点建立新旧索引映射。
  3. 最长递增子序列(LIS):求解旧节点在新列表中位置的最长递增子序列。子序列中的节点意味着“相对顺序未变”,在 DOM 更新时完全不需要移动,从而将 DOM 移动操作降至最低。

5. 组件级渲染机制与异步更新队列

在 Vue 3 中,每个组件实例在挂载(mount)时,都会创建一个专门负责渲染的 ReactiveEffect

// 组件渲染逻辑简化constsetupRenderEffect=(instance,container)=>{constcomponentUpdateFn=()=>{if(!instance.isMounted){// 1. 首次渲染:执行 render 生成 VNode -> 挂载 DOMconstsubTree=(instance.subTree=instance.render.call(instance.proxy));patch(null,subTree,container);instance.isMounted=true;}else{// 2. 重新渲染:生成新 VNode -> 比对 Diff -> 更新 DOMconstnextTree=instance.render.call(instance.proxy);constprevTree=instance.subTree;instance.subTree=nextTree;patch(prevTree,nextTree,container);}};// 创建渲染 Effect,并传入 scheduler 实现批量异步更新consteffect=(instance.effect=newReactiveEffect(componentUpdateFn,()=>queueJob(update)// 数据变化时不立即计算,而是入队微任务));constupdate=(instance.update=()=>effect.run());update();};

异步队列(queueJob)与nextTick

  • 当修改多个refreactive属性时,trigger会触发effect.scheduler(),将更新任务推进全局的queue调度队列。
  • 队列通过Promise.resolve().then(flushJobs)在微任务中统一去重并执行(按组件创建顺序自顶向下刷新),保证组件在同一 Tick 内只会重新 Render 一次。

6. Vue 2 vs Vue 3 全景架构对比总结

架构模块Vue 2 底层方案Vue 3 底层方案
响应式核心Object.defineProperty(Getter/Setter)ES6Proxy+Reflect
依赖管理结构闭包内的Dep实例 +Watcher依赖数组全局targetMap(WeakMap) +ReactiveEffect(Set)
计算属性原理Watcher(lazy: true) +dirty标记ComputedRefImpl+ReactiveEffect链式调度
侦听器原理User Watcher+ 递归traverse()依赖收集watch/watchEffect+onCleanup副作用清理
渲染比对 (Diff)树形全量对比(双端 Diff 算法)Block Tree +PatchFlag靶向比对 +最长递增子序列
内存开销较重(大量的组件级 / 属性级 Watcher 实例化)极轻(无类实例化开销,结构扁平)

🚀 感谢阅读!想了解更多?

📖 我的博客网站 | 记录思考,分享干货
🏡 我的个人主页 | 关于我、开源项目