Vuejs设计与实现 —— 编译层面的优化

简介: Vuejs设计与实现 —— 编译层面的优化

image.png

编译优化

是什么?

编译优化 指的是编译器将 模板(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 的性能消耗,有利于提升初始化渲染的速度

实验性的新编译策略

image.png

从理论上来看,某些情况下确实并不需要 虚拟 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 节点收集

完结

目录
相关文章
|
2月前
|
JavaScript 开发者
解释 Vue 的组件化开发模式及其优势。
解释 Vue 的组件化开发模式及其优势。
36 0
|
2月前
|
存储 开发框架 前端开发
深入探索React:构建动态、交互式前端应用的终极指南
深入探索React:构建动态、交互式前端应用的终极指南
80 0
|
9月前
|
前端开发 JavaScript Serverless
前端工程化的前端性能的性能优化方案的渲染层面优化之CSS/JS优化
渲染是一种非常重要的前端性能优化方案,因为它可以在不同的环境中提高网页的响应速度和可接受性。
64 2
|
11月前
|
设计模式 前端开发 JavaScript
组件库设计 | React组件库Concis开源探索过程中的一些心路历程
本文可能无法从细节层面教会你如何做好一个开源组件库,作者也在不断探索和学习,但是也许会对你有所启发。这篇文章既是分享,也是记录,在写这篇文章的此刻,已经是作者一拍脑袋要做一个开源项目将近半年时间了。半年前作者对于如何开发一个组件库一无所知,对于开源项目也是了解甚少,抱着什么不会学什么的态度,独自一人踏上了开源之旅。
169 2
组件库设计 | React组件库Concis开源探索过程中的一些心路历程
|
存储 前端开发 JavaScript
前端基础知识库vuejs系列三vuex核心原理
现在前端开发中vuejs是大部分前端工程师的首选,只要不是很小的项目都会引用vuex来进行状态管理。最近新开展的项目比较复杂,使用vuex的地方也比较多。在此过程中也遇到了不少问题。如今有时间正好研究下vuex框架源码,深入了解下他的底层实现。
|
JavaScript 前端开发 测试技术
【测试平台开发】十八、vue组件化重构前端代码
【测试平台开发】十八、vue组件化重构前端代码
【测试平台开发】十八、vue组件化重构前端代码
|
缓存 前端开发 iOS开发
挑战21天手写前端框架 day18 引入 antd-mobile5 2分钟画完两个页面
挑战21天手写前端框架 day18 引入 antd-mobile5 2分钟画完两个页面
721 0
挑战21天手写前端框架 day18 引入 antd-mobile5 2分钟画完两个页面
|
前端开发 JavaScript 测试技术
挑战21天手写前端框架 day3 让页面运行起来
挑战21天手写前端框架 day3 让页面运行起来
151 0
挑战21天手写前端框架 day3 让页面运行起来
|
Web App开发 存储 JavaScript
尝鲜少代码高性能的Svelte框架
Svelte 是一种全新的构建用户界面的方法。传统框架(如 React 和 Vue)在浏览器中完成大部分工作,而 Svelte 将这些工作转移到构建应用程序时发生的编译步骤。通过本教程中,你将从头开始构建自己的应用,分别使用 Svelte -Cli 和 云开发平台
2070 10
尝鲜少代码高性能的Svelte框架
|
存储 前端开发 JavaScript
谈谈 React 5种最流行的状态管理库
在本文中,我将一一介绍如何在 React App 中使用 5 种最流行的库/APIS(使用最现代和最新版本的库)如何在 React App程序中使用全局状态管理,并且达到一样的效果。
1905 0