ChatGPT 可用网址,仅供交流学习使用,如对您有所帮助,请收藏并推荐给需要的朋友。
https://ckai.xyz
编译优化 指的是编译器将模板(template)编译为渲染函数(render)的过程中,尽可能的 提取关键信息,以达到生成最优代码的过程。下面我们一起看看Vue3中的编译优化的方式。
标记动态节点
标记动态节点之后,在后续渲染器更新阶段旧可以直接基于动态节点集合,实现对动态节点的 靶向更新 或 定向更新.
patchFlag 属性
在编译器进行编译时,如果判断当前节点是属于 动态节点,就会为这个 vnode 节点打上 patchFlag 标记,也就是添加一个 patchFlag 属性,并且 patchFlag 属性 对应的 数值 代表了当前这个 动态节点的类型,如:
- 数字 1:代表该节点是 动态 的 textContent
- 数字 2:代表该节点是 动态 的 calss 绑定
- 数字 3:代表该节点是 动态 的 style 绑定
- ...
dynamicChildren 属性
dynamicChildren 属性 值对应的是一个数组,其中保存的就是带有 patchFlag 属性 的 vnode 节点,并且带有 dynamicChildren 属性 的 vnode 节点成称为 块,即 Block.
Block 节点
一个 Block 本质上也是一个 虚拟 DOM 节点,只不过它比普通的虚拟节点多了一个用于 存储动态子节点 的 dynamicChildren 属性.
一个 Block 不仅能够收集它的 直接动态子节点,也能收集所有 动态的子代节点,而后续渲染器的更新操作将以 Block 作为更新维度去处理.
什么样的节点会变成 Block 节点?
- 所有模板的 根节点
- 带有 v-if 指令的节点
- 带有 v-for 指令的节点
- 模板中 Frament 节点所包裹的 多根节点
其中 v-if 和 v-for 指令会导致 更新前后模板结构不稳定,不过由于 v-for 指令渲染的是一组子节点,为了更好的表示这一组子节点,就需要使用 Fragment 节点来表达 v-for 指令的渲染结果,并将其作为 Block 节点.
静态提升
静态提升的目的是尽可能减少更新时创建 虚拟 DOM 带来的 性能开销 和 内存占用.
没有静态提升时带来的问题
通常,在响应式数据发生变化时,渲染函数就会重新执行,并产生新的虚拟 DOM 节点,显然纯静态的虚拟节点完全没有必要重新创建,这会带来一定的性能开销.
解决方案
在编译阶段可以 将纯静态节点提升到渲染函数外部,在渲染函数内部保持对静态节点的引用即可,当响应式数据变化引起渲染函数重新执行时,并不会重新创建静态的虚拟节点,这样旧可以避免重复创建静态节点的虚拟 DOM 带来的性能开销.
值得注意的是,静态提升是以树为单位的,毕竟不可能会为每一个小的静态节点进行静态提升,这会导致渲染函数外部对应存储静态节点的变量增多,这也会 占用一定的内存.
预字符串化
基于 静态提升 可以继续采用 预字符串化 的优化手段,即直接将原本需要以树为单位进行静态提升的内容,直接转换为对应基于 DOM 操作的 字符串形式.
预字符串化的优势如下:
- 大块的静态内容可以直接通过 innerHTML 进行设置,在 初始化渲染 时具有一定的性能优势
- 减少创建虚拟节点产生开销的性能
-
减少内存占用
缓存内联事件处理函数
不缓存内联事件函数带来的问题
在模板事件处理函数中,为了一些简单的更新操作,通常会在模板中编写 内联的事件处理函数,例如:
<Comp @change="c = a + b"> ===> function render(ctx){ return h(Com, { // 内联事件处理函数 onChange: () => ctx.c = ctx.a + ctx.b }) }
显然,当 render 函数被重新执行时,都为会 Comp 组件创建一个全新的 props 对象,并且其中的 onChange 事件也是一个全新的函数,这会导致渲染器对 Comp 组件进行更新,造成额外的性能开销。
解决方案
通过为 render 渲染函数传递第二个参数 cache 数组,且这个 cache 数组是来自于组件实例的,因此可以将内联事件处理函数添加到 cache 数组中缓存起来.
当渲染函数重新执行时并创建虚拟 DOM 时,优先从缓存中读取对应的事件处理函数,避免事件处理函数被重新创建,导致 Comp 组件进行无用更新.
v-once 缓存虚拟 DOM
Vue.js2 和 Vue.js3 中都支持 v-once 指令,当前编译器遇到 v-once 指令时,会利用上面提到的 cache 数组来缓存渲染函数的全部或部分执行结果.
v-once 的优势
避免组件更新时重新创建虚拟 DOM 带来的性能开销,因为虚拟 DOM 被缓存了,因此更新时无需重新创建
避免无用的 Diff 开销,这是因为被 v-once 标记的虚拟 DOM 树会被父级 Block 节点收集