Vue.js内部机制深度解析系列
11 篇拆 Vue 2 的源码:响应式怎么装到对象上、模板怎么变成 render 函数、一次赋值到 DOM 更新中间隔着几层队列。这一页说清每篇在回答什么问题,以及哪几条线可以分开读。
这个系列拆的是 Vue 2 的源码,主线只有一条:你改了一个属性,Vue 怎么知道的,又是怎么把这件事变成 DOM 上的一次修改。 11 篇沿着这条链走完,多数篇的末尾会对照一次 Vue 3 把同一件事换成了什么写法。
不必按编号从头读。下面按依赖关系分成三组,组和组之间可以调换顺序。
带着一个具体现象进来的话,先查这张表
| 你遇到的 | 去哪一篇 |
|---|---|
| 给数组下标赋值,页面不动 | 响应式系统核心原理 |
新增对象属性不生效,只能用 Vue.set |
响应式系统核心原理 |
| computed 到底什么时候才重新算 | 依赖收集与追踪机制 |
| 改完数据立刻读 DOM,拿到的还是旧值 | 异步更新与 nextTick(上篇) |
父组件和子组件的 updated 谁先跑 |
异步更新与 nextTick(下篇) |
v-for 不写 key 会怎样 |
Diff 算法深度剖析 |
$emit 的事件为什么不往上传 |
组件系统架构 |
| 同名 mutation 被 commit 时跑了几次 | 状态管理模式 |
| 长列表卡顿,diff 是不是元凶 | 性能优化实践 |
| 写了 template 却报「You are using the runtime-only build」 | 整体架构与设计理念 |
「改了数据,页面就变了」是四篇连起来的一条因果链
这四篇有先后,跳着读会缺环节。
响应式系统核心原理 讲拦截装在哪。Vue 2 给对象逐个 key 装 getter/setter,数组走另一条路 —— 换掉原型上的七个变更方法,所以下标赋值和改 length 拦不住。Vue 3 换成 Proxy 之后这些限制消失了大半,但解构仍然会把响应式丢掉。
依赖收集与追踪机制 讲 getter 被触发之后发生了什么。Dep.target 是一个全局变量配一个栈,一次求值就重建一张依赖表,上一张用来做减法 —— 这一篇解释了为什么「不再读的数据会自动取消订阅」,也解释了 computed 的缓存其实只是一个 dirty 标记。
异步更新与 nextTick(上篇) 从一个能自测的现象开始:赋值那一行返回的时候,DOM 一个字都没改。setter 只是把 watcher 推进队列,回调攒成一批,靠 pending 保证只安排一次冲刷。降级链有四级,前两级是微任务。
异步更新与 nextTick(下篇) 拆队列本身:queueWatcher 靠一张 has 表去重,队列按 watcher id 升序跑(于是父组件先于子组件、用户 watcher 先于渲染 watcher),循环故意不缓存长度,因为跑着跑着还会有新的进来。
模板变成 DOM,中间隔着三篇
模板编译全流程 讲浏览器拿到的从来不是模板而是一个函数:扫字符串、用栈拼出 AST、给 AST 盖两种优化的章、再拼成字符串交给 new Function。v-if 编译成三元,v-for 编译成一次 map。
Virtual DOM 实现详解 讲这个函数的产物。VNode 就是个普通对象,字段少到能一屏列完;真正碰 DOM 的是 patch。这一篇里最反直觉的一节是「虚拟 DOM 换来的不是速度」。
Diff 算法深度剖析 是 patch 里最难的那一段:双端比较的四种命中、key 怎么决定 Vue 认不认得出同一个节点、以及 Vue 3 换成最长递增子序列之后,为什么在乱序列表上移动次数更少。
顺序可以反过来 —— 先读 04 拿到 VNode 的形状,再回头看 05 怎么把它造出来,也讲得通。
剩下三篇互不依赖,想起来再读
整体架构与设计理念 是地图:源码为什么按「跑在哪」分目录而不是按功能分、$mount 为什么被覆盖两次、Vue 为什么是个函数不是 class。不读它不影响读别的,但读完之后你会知道每一篇拆的是哪个目录。
组件系统架构 单独成体系:<child /> 渲染出来的其实是一个占位 VNode,四个钩子挂在这个 VNode 上,patch 走到谁才创建谁的实例。$emit 只是遍历一个数组、provide 只在初始化时取一次值 —— 这两条能解释掉大半的「为什么没生效」。
状态管理模式 顺着 Vuex 3.6.2 看:state 其实是一个隐藏 Vue 实例的 data,getter 是它的 computed,命名空间只是拼在名字前面的一段字符串。最后一节的结论是不装库的时候一个 reactive 对象就够用。
性能优化实践 放在最后,是因为它把前面几篇的结论换成了操作:Object.freeze 省掉的是初始化那一遍遍历、长列表卡的是同时存在的节点数而不是 diff、keep-alive 是拿内存换重建。它也是唯一一篇会拦住你的 —— 量之前先换成生产构建,否则测出来的数字不算数。
这篇是 Vue.js 内部机制深度解析的开头,往下一篇是 Vue.js 整体架构与设计理念。