文章目录

Vue 原理与源码学习笔记

适合目标:系统理解 Vue 3 的底层运行机制,能把响应式系统、编译、渲染、Diff、调度这些高频源码面试题讲清楚。
学习定位:这一份偏“能解释原理、能看懂主干源码、能回答底层问题”。
学习原则:先搭地图,再看实现;先理解数据流和渲染流,再读源码细节;先掌握主干概念,再补优化机制。


目录

  1. 学习总览
  2. 看源码前先建立地图
  3. Vue 整体运行流程
  4. 响应式系统
  5. ref、computed、watch 原理
  6. 编译器与模板编译
  7. 渲染器与虚拟 DOM
  8. Patch 与 Diff
  9. 调度器与异步更新
  10. 组件更新流程
  11. 内置能力原理
  12. Vue 3 设计升级点
  13. 常见源码面试题
  14. 8 小时源码学习规划
  15. 一页速记总结
  16. 背诵口诀
  17. 源码阅读路线

1. 学习总览

1.1 为什么要学 Vue 原理

只会使用 Vue 和能讲清 Vue 的底层,是两个层次。

学原理的价值:

  1. 面试能解释“为什么”
  2. 写组件时更容易避坑
  3. 性能优化更有依据
  4. 看到奇怪更新行为时更容易定位问题

1.2 Vue 原理到底学什么

Vue 底层最核心的主线可以记成:

响应式 -> 依赖收集 -> 触发更新 -> 生成虚拟节点 -> patch DOM

你真正需要掌握的是:

  1. Vue 如何让对象变成响应式
  2. 依赖是怎么被收集的
  3. 数据变化后为什么视图会更新
  4. 模板为什么能变成渲染函数
  5. 新旧节点如何比较
  6. 为什么 DOM 更新是异步批量的

1.3 Vue 源码学习主线

可以记成:

template -> compile -> render function -> vnode -> patch -> DOM

同时还有另一条状态主线:

reactive data -> track -> trigger -> scheduler -> component update

这两条线合起来,才是 Vue 的完整更新机制。

1.4 面试里最常考的底层话题

  1. Vue 3 响应式为什么用 Proxy
  2. refreactive 底层区别
  3. computed 为什么有缓存
  4. watchwatchEffect 区别
  5. Vue 为什么要异步更新 DOM
  6. nextTick 原理是什么
  7. Vue 3 Diff 做了什么优化
  8. Vue 模板是怎么编译的

2. 看源码前先建立地图

2.1 Vue 不是一个单独文件

Vue 3 源码是分模块组织的。

理解层面可以先记住几个重要部分:

  1. reactivity
  2. runtime-core
  3. runtime-dom
  4. compiler-core
  5. compiler-dom

2.2 这些模块负责什么

reactivity

负责响应式能力,例如:

  1. reactive
  2. ref
  3. computed
  4. effect
  5. watch

runtime-core

负责运行时核心逻辑,例如:

  1. 组件初始化
  2. 虚拟 DOM
  3. patch 流程
  4. 调度

runtime-dom

负责和浏览器 DOM 交互。

compiler-core

负责把模板编译成渲染函数的核心逻辑。

compiler-dom

处理浏览器 DOM 相关模板编译细节。

2.3 阅读源码最容易犯的错

  1. 上来就啃 Proxy 细节
  2. 看一堆函数却不知道在哪个阶段
  3. 把 Vue 2 和 Vue 3 的实现混在一起
  4. 只背结论,不知道流程顺序

2.4 正确阅读策略

推荐顺序:

  1. 先理解整体流程
  2. 再看响应式
  3. 再看编译
  4. 再看渲染和 patch
  5. 最后看调度与优化

3. Vue 整体运行流程

3.1 从模板到 DOM 的大流程

Vue 组件运行可以先简化成:

  1. 读取组件定义
  2. 模板编译成 render 函数
  3. render 函数执行生成 vnode
  4. patch 把 vnode 挂载到真实 DOM
  5. 响应式数据变化后重新执行 render
  6. 对新旧 vnode 做比较并更新 DOM

3.2 什么是 vnode

vnode 就是虚拟节点对象,用来描述应该渲染成什么。

它通常包含:

  1. 类型
  2. props
  3. children
  4. key
  5. patch 相关标记

3.3 Vue 的更新主线

可以浓缩成一句话:

数据变化 -> 触发副作用函数重新执行 -> 生成新 vnode -> patch

3.4 为什么要把编译和运行时分开

因为模板编译发生在更早阶段,而渲染更新发生在运行阶段。

好处:

  1. 可以提前做优化
  2. 运行时负担更小
  3. 模板表达能力更强

4. 响应式系统

Vue 3 最核心的底层能力就是响应式系统。

4.1 响应式系统解决什么问题

它解决的是:

谁依赖了某个数据,数据变了要通知谁重新执行

这里有两个核心动作:

  1. track:依赖收集
  2. trigger:依赖触发

4.2 effect 是什么

可以把 effect 理解成:

一个会在依赖变化时重新执行的副作用函数

在组件渲染层面,组件的 render 过程本质上也会被包装成响应式 effect。

4.3 reactive 的本质

reactive 会返回一个 Proxy 代理对象。

当你:

  1. 读取属性时,触发 get,进行依赖收集
  2. 修改属性时,触发 set,进行依赖通知

4.4 为什么 Vue 3 用 Proxy

这是超级高频题。

答题思路:

  1. Proxy 能代理整个对象,而不是逐个属性定义 getter/setter
  2. 对新增属性、删除属性、数组索引、length 等支持更自然
  3. API 更统一,扩展性更好

4.5 依赖收集怎么理解

当某个响应式属性在 effect 中被读取时:

  1. 当前激活的 effect 会被记录下来
  2. 这个属性和这个 effect 建立依赖关系

可以理解成:

属性记住了哪些 effect 用过自己

4.6 触发更新怎么理解

当响应式属性被修改时:

  1. 找到依赖它的 effect 集合
  2. 依次通知这些 effect 重新执行

4.7 targetMap 怎么理解

源码层可以理解成一个多层映射结构:

  1. 某个目标对象
  2. 对应某个 key
  3. key 下存依赖这个属性的 effect 集合

可以粗略理解为:

target -> key -> dep(Set<effect>)

4.8 为什么要用 Set 存依赖

因为:

  1. 去重方便
  2. 查找和添加方便
  3. 不会重复收集同一个 effect

4.9 响应式系统记忆口诀

读的时候收集依赖,写的时候触发依赖。


5. ref、computed、watch 原理

5.1 ref 原理怎么理解

ref 本质上是一个带 .value 的响应式包装对象。

为什么要这样设计:

  1. 基本类型不能直接被 Proxy 代理
  2. 用一个对象壳把值包起来,就能统一响应式处理

所以你可以把它理解成:

{
  value: 某个值
}

只不过这个 value 的读取和设置被响应式系统接管了。

5.2 reactive 和 ref 底层区别

  1. reactive 主要代理对象本身
  2. ref 主要通过 .value 这个访问点做依赖收集和触发

5.3 computed 为什么有缓存

computed 的核心是:

  1. 内部也基于 effect
  2. 但它是“惰性求值”的
  3. 只有依赖变化时才会标记为脏
  4. 下次读取时才重新计算

你可以把它理解成:

  1. 第一次读,执行 getter,缓存结果
  2. 依赖变了,不马上重算,只把它标记为脏
  3. 下次再读时,才真正重新计算

这就是缓存的来源。

5.4 watch 原理怎么理解

watch 会:

  1. 监听指定源
  2. 比较新旧值
  3. 当值变化时执行回调

它比 watchEffect 更明确,因为监听目标是显式指定的。

5.5 watchEffect 原理怎么理解

watchEffect 在执行过程中自动收集依赖。

可以理解成:

  1. 先执行一次函数
  2. 函数里读到的响应式数据都会被收集
  3. 这些依赖变化时,函数重新执行

5.6 watch 和 watchEffect 区别

  1. watch 关注“监听谁”
  2. watchEffect 关注“执行过程中用了谁”
  3. watch 更可控
  4. watchEffect 更自动

5.7 cleanup 怎么理解

无论是 watch 还是 watchEffect,都常和清理逻辑相关。

典型场景:

  1. 取消上一次请求
  2. 清除定时器
  3. 解绑订阅

5.8 记忆口诀

ref 是 value 壳,computed 是带缓存的惰性 effect,watch 是显式监听,watchEffect 是自动收集。


6. 编译器与模板编译

6.1 为什么 Vue 需要编译器

因为 Vue 支持模板:

<div>{{ msg }}</div>

模板本身不能直接运行,必须先变成 JavaScript 渲染函数。

6.2 编译大流程

模板编译可以理解成三步:

  1. parse:把模板解析成 AST
  2. transform:对 AST 做转换和优化
  3. generate:生成 render 函数字符串 / 代码

6.3 AST 是什么

AST 是抽象语法树。

你可以把它理解成:

模板结构的树形数据表示

6.4 transform 阶段做什么

  1. 分析节点类型
  2. 处理指令
  3. 标记动态节点
  4. 做一些编译优化准备

6.5 generate 阶段做什么

把处理后的 AST 生成 render 函数。

6.6 render 函数是什么

render 函数本质上是:

根据当前状态,返回 vnode 树的函数

6.7 编译优化为什么重要

Vue 3 的重要优势之一,就是编译阶段能提前识别:

  1. 哪些节点是静态的
  2. 哪些节点是动态的
  3. 哪些片段值得单独追踪

这样运行时 patch 的成本会更低。

6.8 Patch Flags 怎么理解

Vue 3 会在编译阶段给某些动态节点打上补丁标记。

作用:

  1. 让运行时更快知道哪里可能变
  2. 减少无意义对比

6.9 Block Tree 怎么理解

Vue 3 会把真正动态的节点集中关注,帮助运行时优化更新范围。

你不必死记细节,但面试里可以答:

  1. Vue 3 通过编译阶段分析动态节点
  2. 配合 patch flags 和 block tree,减少运行时无效 Diff

6.10 编译主线口诀

模板先 parse 成 AST,再 transform 做分析优化,最后 generate 出 render。


7. 渲染器与虚拟 DOM

7.1 渲染器在做什么

渲染器负责把 vnode 变成真实界面。

主要工作:

  1. 首次挂载
  2. 更新 patch
  3. 卸载节点

7.2 runtime-core 和 runtime-dom 的关系

runtime-core

更偏平台无关的核心渲染逻辑。

runtime-dom

负责浏览器 DOM 环境的实际操作。

7.3 为什么要有虚拟 DOM

虚拟 DOM 不是为了“绝对更快”,而是为了:

  1. 提供跨平台抽象
  2. 让声明式渲染更自然
  3. 让更新流程可以统一处理

7.4 vnode 和真实 DOM 的关系

  1. vnode 是描述对象
  2. 真实 DOM 是浏览器节点
  3. patch 负责把 vnode 的变化映射到真实 DOM

7.5 挂载过程怎么理解

首次渲染时:

  1. 执行 render,拿到 vnode
  2. 遍历 vnode
  3. 创建真实节点
  4. 插入页面

7.6 更新过程怎么理解

更新时:

  1. 再次执行 render,拿到新 vnode
  2. 和旧 vnode 比较
  3. 找出差异
  4. 最小化更新真实 DOM

7.7 渲染器记忆口诀

render 产出 vnode,patch 负责挂载和更新。


8. Patch 与 Diff

Vue 的 Diff 面试高频程度很高,尤其是 key、最长递增子序列、静态提升这些点。

8.1 Patch 是什么

patch 可以理解成:

根据新旧 vnode 的差异,把真实界面更新到最新状态

8.2 Diff 为什么需要 key

在列表更新中,如果没有稳定 key,框架很难正确识别每个节点身份。

key 的好处:

  1. 节点复用更准确
  2. 状态更稳定
  3. 移动成本更容易判断

8.3 Vue 3 Diff 的常见思路

理解版可以记成:

  1. 先比头
  2. 再比尾
  3. 中间乱序部分再进一步处理
  4. 必要时借助最长递增子序列减少移动

8.4 最长递增子序列 LIS 为什么会出现

在处理中间乱序列表时,Vue 3 会尽量找出“已经相对稳定、不需要移动”的那部分节点。

这个“尽量少移动”的优化,背后就会用到最长递增子序列思想。

你面试里不用展开算法证明,但要会说:

  1. Vue 3 在 keyed children Diff 中会尽量减少节点移动
  2. LIS 是为了找出不需要移动的最长稳定子序列

8.5 Vue 3 Diff 优化点怎么答

常见答法:

  1. 头尾指针优化
  2. 针对稳定 key 做更高效比较
  3. 对中间乱序部分结合映射表和 LIS 优化移动
  4. 配合编译阶段 patch flags 减少无意义对比

8.6 patchChildren 怎么理解

不同 children 形态会有不同处理:

  1. 文本
  2. 数组

更新时要考虑新旧 children 之间的转换。

8.7 为什么 Vue 3 比 Vue 2 更容易优化

因为 Vue 3 不仅靠运行时 Diff,还结合了编译时信息。

8.8 Diff 记忆口诀

稳定 key 定身份,头尾先比较,中间再精算,LIS 减少移动。


9. 调度器与异步更新

9.1 Vue 为什么不每次改数据都立刻改 DOM

如果每次小改动都立刻刷新 DOM,会带来:

  1. 频繁重复更新
  2. 性能浪费
  3. 同一轮里很多无意义渲染

所以 Vue 选择:

把同一轮更新收集起来,再批量执行

9.2 调度器解决什么问题

调度器负责:

  1. 收集组件更新任务
  2. 去重
  3. 排序
  4. 在合适时机统一执行

9.3 job 队列怎么理解

当组件需要更新时,不一定立刻重新渲染,而是:

  1. 生成一个更新 job
  2. 放入队列
  3. 在微任务时机统一刷新

9.4 为什么 nextTick 通常和微任务有关

因为 Vue 的异步刷新通常会尽量借助微任务队列,让更新更快但又能合并。

所以 nextTick 可以理解成:

等当前这轮更新队列刷新完后,再执行回调

9.5 批量更新的收益

  1. 减少重复 render
  2. 减少重复 patch
  3. 同一轮逻辑内更容易拿到稳定的最终结果

9.6 nextTick 原理答题模板

  1. Vue 会把更新任务异步批量处理
  2. nextTick 本质是等待这一轮 DOM 更新完成
  3. 常借助微任务来安排回调执行

9.7 调度器口诀

更新先进队列,统一去重刷新,nextTick 等这一轮刷完。


10. 组件更新流程

10.1 组件为什么会更新

常见触发源:

  1. 自身响应式状态变化
  2. 父组件传入的 props 变化
  3. Context 类共享依赖变化,如 provide/inject 链上的值变化
  4. 强制刷新或其他内部机制触发

10.2 组件更新的大流程

可以理解成:

  1. 状态变化
  2. trigger 找到组件渲染 effect
  3. 交给调度器排队
  4. effect 重新执行
  5. 再次调用 render 生成新 vnode
  6. patch 新旧 vnode
  7. 更新真实 DOM

10.3 组件渲染 effect

组件的渲染过程,本质上也会包装成一个 effect。

这样当模板里依赖的响应式数据变了,组件就知道自己该重新渲染。

10.4 父子更新关系

更新时通常要注意:

  1. 父组件变了,子组件可能也参与更新
  2. 但如果 props 没变、依赖没变,也可能跳过某些无意义工作

10.5 为什么局部更新重要

Vue 的性能优化很多时候不是“更快的算法”,而是:

让真正需要更新的范围尽量小


11. 内置能力原理

11.1 KeepAlive 原理化理解

KeepAlive 的核心不是普通显示隐藏,而是:

  1. 缓存组件实例
  2. 缓存对应子树
  3. 切换时避免重新挂载

11.2 Teleport 原理化理解

Teleport 让 vnode 的逻辑归属还在当前组件树里,但真实 DOM 可以挂到别处。

11.3 Suspense 原理化理解

当组件存在异步依赖还没准备好时,可以先显示 fallback,等就绪后再切换正式内容。

11.4 Transition 原理化理解

在 vnode 进入和离开时,协调 class 和时机,帮助动画过渡。

11.5 为什么这些内置能力重要

因为它们体现了 Vue 对真实 UI 场景的统一抽象,而不只是单纯模板渲染。


12. Vue 3 设计升级点

12.1 从 Vue 2 到 Vue 3 的核心升级

从原理视角看,重点是:

  1. 响应式系统升级为 Proxy
  2. Composition API 带来更强逻辑组织能力
  3. 编译优化更强
  4. 渲染和 Diff 优化更细
  5. TypeScript 体验更好

12.2 为什么 Vue 3 编译优化更重要

因为 Vue 3 不只是运行时框架,它更强调:

编译时尽可能多做分析,运行时尽可能少做无效工作

12.3 为什么说 Vue 3 更适合大型项目

可以这样答:

  1. Composition API 更适合复杂逻辑拆分
  2. TS 支持更友好
  3. 编译和运行时整体优化更成熟

13. 常见源码面试题

13.1 Vue 3 为什么用 Proxy

答:

  1. Proxy 可以拦截更完整的对象操作
  2. 对新增、删除、数组索引等场景支持更自然
  3. 相比 Object.defineProperty 更灵活

13.2 computed 为什么有缓存

答:

  1. computed 内部基于 effect
  2. 采用惰性求值
  3. 依赖不变时直接复用旧结果
  4. 依赖变化时只标记脏,等下次读取再重算

13.3 watch 和 watchEffect 区别

答:

  1. watch 显式指定监听源
  2. watchEffect 自动收集依赖
  3. watch 更可控
  4. watchEffect 更适合快速副作用联动

13.4 nextTick 为什么存在

答:

  1. Vue 会合并同一轮更新并异步刷新 DOM
  2. nextTick 让你在 DOM 更新完成后执行逻辑

13.5 Vue 模板是怎么变成 render 的

答:

  1. 模板先被解析成 AST
  2. 再做 transform 分析和优化
  3. 最后 generate 成 render 函数

13.6 Vue 3 Diff 有什么优化

答:

  1. 头尾指针优化
  2. 中间乱序比较优化
  3. 使用 LIS 尽量减少移动
  4. 配合编译时 patch flags 减少无意义比较

13.7 为什么 Vue 要异步更新

答:

  1. 避免同一轮里重复渲染
  2. 合并多次更新
  3. 提高性能和一致性

13.8 ref 和 reactive 的底层区别

答:

  1. reactive 基于 Proxy 代理对象
  2. ref 通过 .value 做依赖收集和触发
  3. ref 更适合包装基本类型

13.9 面试回答总模板

源码题建议按这个顺序答:

  1. 先说它解决什么问题
  2. 再说核心机制
  3. 再说执行流程
  4. 最后说收益和代价

14. 8 小时源码学习规划

第 1 小时:搭地图

目标:

  1. 搞懂 reactivityruntime-corecompiler-core
  2. 建立 template 到 DOM 的主流程

第 2-3 小时:响应式系统

目标:

  1. 搞清楚 track/trigger
  2. 理解 effect
  3. 理解 ref/reactive/computed/watch

必须会:

  1. 为什么 Vue 3 用 Proxy
  2. computed 为什么有缓存

第 4 小时:编译器

目标:

  1. 理解 parse/transform/generate
  2. 理解 AST
  3. 理解 patch flags

第 5 小时:渲染器与 patch

目标:

  1. 理解 vnode
  2. 理解挂载与更新
  3. 理解 patch 主流程

第 6 小时:Diff

目标:

  1. 掌握 key 作用
  2. 掌握头尾比较
  3. 理解 LIS 的意义

第 7 小时:调度与 nextTick

目标:

  1. 理解 job 队列
  2. 理解异步更新
  3. 理解 nextTick

第 8 小时:复盘 + 面试表达

建议:

  1. 自己画出响应式依赖图
  2. 自己讲清 render 到 patch
  3. 自己讲清 Diff 和 nextTick
  4. 自己讲清 Vue 3 核心升级点

15. 一页速记总结

15.1 响应式

  1. gettrack
  2. settrigger
  3. effect 是依赖重新执行的载体

15.2 编译

  1. parse
  2. transform
  3. generate

15.3 渲染

  1. render 产出 vnode
  2. patch 负责挂载和更新

15.4 Diff

  1. key 定身份
  2. 头尾优化
  3. 中间乱序用映射和 LIS

15.5 调度

  1. 更新先入队
  2. 队列去重
  3. 微任务统一刷新
  4. nextTick 等 DOM 更新完成

15.6 Vue 3 升级点

  1. Proxy 响应式
  2. Composition API
  3. 编译优化
  4. 更好的 TS 支持

16. 背诵口诀

16.1 响应式口诀

读就收集,写就触发;effect 负责重跑。

16.2 编译口诀

模板先成 AST,转换做优化,最后产出 render。

16.3 Diff 口诀

key 定身份,头尾先比较,中间看映射,LIS 减少移动。

16.4 调度口诀

更新先入队,统一再刷新,nextTick 等这一轮 DOM 刷完。


17. 源码阅读路线

17.1 第一阶段:只看概念主线

先回答:

  1. Vue 如何响应式
  2. 模板如何变成 render
  3. vnode 如何 patch 到 DOM
  4. 为什么 DOM 更新不是同步立刻执行

17.2 第二阶段:按模块看

推荐顺序:

  1. 先看 reactivity
  2. 再看 compiler-core
  3. 再看 runtime-core
  4. 最后看调度和 patch 优化

17.3 第三阶段:带问题看源码

不要问“这一行干嘛”,而要问:

  1. 它属于哪个阶段
  2. 它解决哪个问题
  3. 如果没有它会怎样

17.4 阅读时最值得记录的内容

  1. 核心数据结构
  2. 关键流程函数
  3. 编译和运行时边界
  4. 高频面试表达模板

17.5 最后给你的建议

学 Vue 源码时,最重要的不是把每个细节都背下来,而是把这 5 条线讲顺:

  1. 为什么 Vue 3 用 Proxy
  2. 响应式如何 track/trigger
  3. 模板如何编译成 render
  4. vnode 如何 patch 到 DOM
  5. 为什么更新是异步批量的

只要你能把这 5 条线讲清楚,Vue 原理面试就已经很稳了。