关于前端:你知道-Vue3-中的编译优化吗

35次阅读

共计 2610 个字符,预计需要花费 7 分钟才能阅读完成。

前言

相比于 Vue2,Vue3 通过进行各种编译优化,极大的晋升了 Vue 的整体性能,这也使得 Vue3 在编译和更新阶段取得了更快的速度晋升!

接下来咱们一起看看吧!

编译优化

是什么?

编译优化 指的是编译器将 模板(template) 编译为 渲染函数(render) 的过程中,尽可能的 提取要害信息 ,以达到 生成最优代码 的过程。

为什么须要?

传统的 Diff 算法会存在很多无意义的比照操作

在比照 新旧 两颗 虚构 DOM 时,总是要依照 虚构 DOM层级构造 “ 一层一层 ” 进行遍历,而后其中某些内容的遍历比照是齐全没必要的,例如:

<div id="foo">
    <p class="bar">{{text}}</p>
</div>

其中惟一可能变动的就是 <p> 标签中的 text 值,当响应式数据 text 产生批改时,最高效的更新形式就是间接更新 <p> 标签对应的文本内容,然而对于 传统 Diff 算法 而言,会先依据 render 函数生成 新的虚构 DOM,而后再比照 新旧虚构 DOM 的形式:

  • 比照 div 节点,以及该节点的属性和子节点
  • 比照 p 节点,以及该节点的属性和子节点
  • 比照 p 节点的文本节点,发现文本节点发生变化,于是更新文本节点

    编译思路

    Vue.js3 编译优化的思路起源就是,跳过这些无意义的对操作,进一步的晋升 VueDiff 算法的比照性能:

  • 模板的构造绝对稳固,在编译阶段尽可能提取要害信息(如:标记动态节点、动静节点)
  • 基于要害信息,通过编译器间接生成对应的原生 DOM 操作代码,缩小生成 虚构 DOM 的性能耗费,有利于晋升初始化渲染的速度

    实验性的新编译策略

从实践上来看,某些状况下的确并不需要 虚构 DOM,(即 No Virtual DOM),但在 Vue.js3 中依然抉择保留虚构 DOM,并接受其带来的性能开销,次要是思考到 渲染函数的灵活性Vue.js2 的兼容性 问题.

感兴趣能够去理解下,将来 Vue 会提供的一些新个性,不过这并不是本文的核心内容,State of Vue 2022- 尤雨溪

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-ifv-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.js2Vue.js3 中都反对 v-once 指令,以后编译器遇到 v-once 指令时,会利用下面提到的 cache 数组来缓存渲染函数的全副或局部执行后果.

v-once 的劣势

  • 防止组件更新时从新创立虚构 DOM 带来的性能开销,因为虚构 DOM 被缓存了,因而更新时无需从新创立
  • 防止无用的 Diff 开销,这是因为被 v-once 标记的虚构 DOM 树会被父级 Block 节点收集

最初

以上就是本文的全部内容,心愿对你所有帮忙!!!

本文参加了 SegmentFault 思否写作挑战赛,欢送正在浏览的你也退出。

正文完
 0