Vue 模板编译机制
在 Vue 中,模板编译是将 Vue 模板转换为渲染函数的过程。Vue 模板是一种 HTML 扩展语言,支持添加一些特殊的指令和表达式。例如,可以使用 v-bind 指令绑定 HTML 属性,使用 v-on 指令添加事件处理程序,使用{{ }}表达式插入动态数据等。
Vue 的模板编译过程可以分为三个阶段
- 解析阶段
将模板解析为抽象语法树(AST)。在这个阶段中,Vue 会解析模板中的所有指令和表达式,并生成对应的 AST 节点。 - 优化阶段
对 AST 进行优化。在这个阶段中,Vue 会对 AST 进行静态分析和优化,减少不必要的计算,生成优化后的 AST。 - 代码生成阶段
将 AST 转换为渲染函数。在这个阶段中,Vue 会将生成的 AST 转换为可执行的 JavaScript 代码,并返回渲染函数。
Vue 更新
Vue 使用响应式系统来收集依赖和派发更新,当模板中数据发生变化时,组件的 render 函数会被作为数据的依赖而被触发,只不过这个触发并不是立刻的,因为模板中会引用很多数据,render 同时是这些所有数据的依赖项,如果 render 每次都立刻执行,则会造成多次重复渲染而消耗性能。
实际上 render 是被 update 调用的,而 update 又是 Watcher 调用的,而 Watcher 在收到 Dep 的派发更新时会把自身交给 Scheduler ,由 Scheduler 负责对其去重并通过 nextTick() 将这些 Watcher 包装成微任务放入到事件循环中等待调用。
render 执行输出的结果是一颗新的虚拟 DOM 树,然后 update 会通过 patch 函数将它与旧的虚拟 DOM 树进行对比,diff 和真实 DOM 的操作过程既是在 patch 函数中进行。
所以,其实 Vue 的整个更新任务(构建虚拟 DOM ,diff,操作真实 DOM)可以算作一个整体,这个整体被当作微任务来处理,这也就是 Vue 异步更新的原理。
Vue 渲染机制
在 Vue 中,渲染是将 Vue 实例的状态转换为 DOM 的过程。当 Vue 实例的状态变化时,会触发重新渲染,自动更新视图。
虚拟 DOM (Virtual DOM,简称 VDOM) 是一种编程概念,意为将目标所需的 UI 通过数据结构“虚拟”地表示出来,保存在内存中,然后将真实的 DOM 与之保持同步。
一个运行时渲染器将会遍历整个虚拟 DOM 树,并据此构建真实的 DOM 树。这个过程被称为挂载 (mount)。
如果我们有两份虚拟 DOM 树,渲染器将会有比较地遍历它们,找出它们之间的区别,并应用这其中的变化到真实的 DOM 上。这个过程被称为更新 (patch),又被称为“比对”(diffing) 或“协调”(reconciliation)。
Vue 组件挂载时会发生
编译:Vue 模板被编译为渲染函数:即用来返回虚拟 DOM 树的函数。这一步骤可以通过构建步骤提前完成,也可以通过使用运行时编译器即时完成。
挂载:运行时渲染器调用渲染函数,遍历返回的虚拟 DOM 树,并基于它创建实际的 DOM 节点。这一步会作为响应式副作用执行,因此它会追踪其中所用到的所有响应式依赖。
更新:当一个依赖发生变化后,副作用会重新运行,这时候会创建一个更新后的虚拟 DOM 树。运行时渲染器遍历这棵新树,将它与旧树进行比较,然后将必要的更新应用到真实 DOM 上去。
Vue 的渲染过程可以分为以下几个步骤
- 创建虚拟 DOM
Vue 会通过渲染函数获取虚拟 DOM,用于描述界面结构。 - 更新虚拟 DOM
当视图需要更新时,Vue 会通过 diff 算法比较新旧虚拟 DOM 的差异,生成可以最小化更新的操作序列。 - 应用更新
最后,Vue 会根据生成的操作序列,将变化应用到实际的 DOM 上。
Vue 官方文档图

为什么 Vue 默认推荐使用模板,不是渲染函数呢?
Vue 模板会被预编译成虚拟 DOM 渲染函数。Vue 也提供了 API 使我们可以不使用模板编译,直接手写渲染函数。在处理高度动态的逻辑时,渲染函数相比于模板更加灵活,因为你可以完全地使用 JavaScript 来构造你想要的 vnode。
原因
模板更贴近实际的 HTML。这使得我们能够更方便地重用一些已有的 HTML 代码片段,能够带来更好的可访问性体验、能更方便地使用 CSS 应用样式,并且更容易使设计师理解和修改。
由于其确定的语法,更容易对模板做静态分析。这使得 Vue 的模板编译器能够应用许多编译时优化来提升虚拟 DOM 的性能表现。
在实践中,模板对大多数的应用场景都是够用且高效的。渲染函数一般只会在需要处理高度动态渲染逻辑的可重用组件中使用。
Vue 与 React 更新机制区别
- 触发更新机制
- Vue 是通过响应式系统自动及时的进行触发
- React 则是通过用户更改状态的操作然后进行一系列调度来触发更新
- 任务的区别
- Vue 会将任务包装成微任务
- React 则是将其包装成宏任务
虽然都是异步任务,但它们有很大区别。在事件循环中,如果有微任务存在则会先一直执行微任务,直到把微任务队列清空,然后再执行宏任务,并且在每个宏任务执行完毕后,会立即检查并执行所有微任务,然后再进行下一个宏任务的执行。
先明确一点:异步任务执行时是由主线程进行执行的,所以此时它们已经相当于是同步执行了(这个异步实际指的是异步任务在任务队列里面等待的时候不会影响主线程的执行)
微任务执行时不会穿插其它任务(比如浏览器渲染),所以当有大量微任务堆积时可能就会阻塞浏览器渲染(异步任务),但执行完一个宏任务时如果遇到浏览器需要渲染,则不会继续执行下一个宏任务而是转去进行浏览器渲染然后开启新的一轮事件循环。
因为 React 的 Fiber 架构的出现就是为了能够随时打断,把控制权交给主线程,所以 React 采用的是宏任务,而不是会一股脑 “ 全冲完 ” 的微任务,这样可以避免微任务过多而导致的任务堆积和性能问题。
也正是因为 Vue 的理念是追求响应性和即时效果并避免过多的渲染,所以它采用微任务,及时把更新任务处理完,最后让浏览器渲染一次即可。
其实现如今,React 和 Vue 都不是完全使用某一种任务,在一些情况下 React 也会使用微任务,Vue 也是如此,它们的目标都是想要结合自身情况来创造一个更优秀的框架。
- diff 算法的不同
- Vue 采用双端对比
- React 使用的是 Reconciliation 算法。