vue3性能提升主要通过哪几方面?

2024-04-02 19:04

本文主要是介绍vue3性能提升主要通过哪几方面?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

📝个人主页:爱吃炫迈
💌系列专栏:Vue
🧑‍💻座右铭:道阻且长,行则将至💗

文章目录

  • 编译优化
    • diff算法优化
      • 提取动态节点
    • 静态提升
    • 预字符串化
    • 缓存内联事件处理函数
    • SSR优化
  • 源码体积
  • 响应式系统


编译优化

编译优化指的是编译器将模版编译成渲染函数的过程中,尽可能多的提取关键信息,并以此指导生成最优代码的过程。
编译优化的策略与具体实现是由框架的设计思路决定的,不同的框架具有不同的设计思路,因此编译优化的策略也不尽相同。
但优化的方向是基本一致的,就是尽可能的区分静态内容动态内容,并根据不同的内容采用不同的优化策略。

diff算法优化

传统的Diff算法,在对比两颗虚拟DOM树的时候,总是要按照虚拟DOM的层级“一层一层”地遍历。
举个栗子:

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

传统Diff在响应式数据text发生变化时,会生成一棵新的虚拟DOM树,传统的Diff算法对比新旧两颗虚拟DOM树的过程如下:

  1. 对比div节点,以及该节点的属性和子节点
  2. 对比p节点,以及该节点的属性和子节点
  3. 对比p节点的文本子节点,如果文本子节点的内容变了,则更新之,否则什么都不做

在上面这段模版中,变得只有p标签的文本子节点的内容,也就是说,其实当响应式数据变化的时候,最高效的更新方式就是直接设置p标签的文本内容,但传统的Diff算法显然做不到这么高效,存在很多无意义的对比操作,如果能跳过这些无意义的操作,性能会有很大的提升。这也就是vue3的编译优化的思路。

vue3的编译器可以分析出很多关键信息,例如哪些节点的静态的,哪些节点是动态的,编译器会将编译时得到的关键信息“附着”在它生成的虚拟DOM上,这些信息回通过虚拟DOM传递给渲染器。最终渲染器会根据这些关键信息执行“快捷路径”,从而提升运行时的性能。

所以vue3在diff算法中增加了静态标记pathFlag,这个标记表明这个节点是动态的会发生改变,下次发生改变的时候直接找该地方进行比较。
举个例子:

<div><div>foo</div><p>{{ bar }}</p>
</div>

传统的虚拟DOM描述上述模版:

const vnode = {tag: 'div',children: [{ tag: 'div', children: 'foo' },{ tag: 'p', children: ctx.bar },],
};

上述模版中只有{{ bar }}是动态的内容,但是虚拟DOM中没有任何标志能体现出节点的动态性。
但是经过编译优化后,编译器会将它提取到的关键信息“附着”在虚拟DOM节点上:

const vnode = {tag: 'div',children: [{ tag: 'div', children: 'foo' },{ tag: 'p', children: ctx.bar, patchFlag: 1 }, // 这是动态节点,],
};

可以看到用于描述p标签的虚拟DOM拥有一个额外的属性,patchFlag,它的值是一个数字。只要虚拟DOM存在该属性,我们就认为他是一个动态节点。这里的patchFlag就是所谓的补丁标志。

可以将补丁标志理解为一系列数字标记,并根据数字值的不同赋予它不同的含义,例如:

const PatchFlags {TEXT = 1,// 动态的文本节点CLASS = 1 << 1,  // 2 动态的 classSTYLE = 1 << 2,  // 4 动态的 stylePROPS = 1 << 3,  // 8 动态属性,不包括类名和样式FULL_PROPS = 1 << 4,  // 16 动态 key,当 key 变化时需要完整的 diff 算法做比较HYDRATE_EVENTS = 1 << 5,  // 32 表示带有事件监听器的节点STABLE_FRAGMENT = 1 << 6,   // 64 一个不会改变子节点顺序的 FragmentKEYED_FRAGMENT = 1 << 7, // 128 带有 key 属性的 FragmentUNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有 key 的 FragmentNEED_PATCH = 1 << 9,   // 512DYNAMIC_SLOTS = 1 << 10,  // 动态 soltHOISTED = -1,  // 特殊标志是负整数表示永远不会用作 diffBAIL = -2 // 一个特殊的标志,指代差异算法
}

有了PatchFlag这个信息,就可以在虚拟DOM的创建阶段将它的动态子节点提取出来,并存到该虚拟节点的dynamicChildren数组中:

const vnode = {tag: 'div',children: [{ tag: 'div', children: 'foo' },{ tag: 'p', children: ctx.bar, patchFlag: patchFlags.TEXT }, // 这是动态节点,],// 数组dynamicChildren:[// p节点具有patchFlag属性,所以是动态节点{ tag: 'p', children: ctx.bar, patchFlag: patchFlags.TEXT }
]
};

上面的虚拟DOM对象与普通的虚拟DOM对象相比,多了一个额外的dynamicChildren属性。vue3中将拥有这个属性的虚拟DOM对象称为“块”,即Block。
所以一个Block的本质也是一个虚拟DOM对象,只不过比普通的虚拟DOM对象多出了一个用于存储动态子节点的dynamicChildren属性。
现在有个问题:就是如果不是Block的直接子节点,还能够收集吗?答案是yes!
举例说明:

<div><div><p>{{ bar}}</p></div>
</div>

最外层div标签对应的Block能够将p标签收集到它的dynamicChildren数组中:

const vnode = {tag: 'div',children: [{tag: 'div',children: [{ tag: 'p', children: ctx.bar, patchFlag: patchFlags.TEXT }],},],dynamicChildren: [// p节点具有patchFlag属性,所以是动态节点{ tag: 'p', children: ctx.bar, patchFlag: patchFlags.TEXT },],
};

有了Block这个概念之后,渲染器的更新操作将会以Block为维度。也就是说,当渲染器在更新一个Block的时候,会忽略虚拟DOM对象的children数组,而是直接找到虚拟DOM对象的dynamicChildren数组,并只更新该数组中的动态节点。
这样在更新的时候就实现了跳过静态内容,只更新动态内容。同时,由于动态节点中存在对应的补丁标志,所以在更新动态节点的时候,也能够做到靶向更新。例如,当一个动态节点的pathFlag值得数字为1时,我们知道他只存在动态的文本节点,所以只需要更新它的文本内容即可。

提取动态节点

未完待续·······

静态提升

静态提升能够减少更新时西黄建虚拟DOM带来的性能开销和内存占用。
举个例子:

<div><p>static text</p><p>{{title}}</p>
</div>

在没有静态提升的情况下,它对应的渲染函数是:

function render() {return (openBlock(),createBlock('div',null,[createVNode('p', null, 'static text'),createVNode('p', null, ctx.title, 1 /* TEXT */)]))
}

可以看到,在这段虚拟 DOM 的描述中存在两个 p 标签,一个是纯静态的,而另一个拥有动态文本。
当响应式数据 title 的值发生变化时,整个渲染函数会重新执行,并产生新的虚拟 DOM 树。
这个过程 有一个明显的问题,即纯静态的虚拟节点在更新时也会被重新创建一 次。很显然,这是没有必要的,所以我们需要想办法避免由此带来的性能开销。
而解决方案就是所谓的**“静态提升”**,即把纯静态的节点提升到渲染函数之外,如下面的代码所示:

// 把静态节点提升到渲染函数之外const hoist1 = createVNode('p', null, 'text')function render() {return (openBlock(), createBlock('div', null, [hoist1, // 静态节点引用createVNode('p', null, ctx.title, 1 /* TEXT */)]))
}

可以看到,当把纯静态的节点提升到渲染函数之外后,在渲染函 数内只会持有对静态节点的引用。当响应式数据变化,并使得渲染函 数重新执行时,并不会重新创建静态的虚拟节点,从而避免了额外的 性能开销。

有一个问题需要注意:
静态提升是以树为单位的,举例说明:

<div><section><p><span>abc</span></p></section >
</div>

在上面这段模板中,除了根节点的** div 标签会作为 Block 角色而不可被提升**之外,整个 元素及其子代节点都会被提升。
如果我们把上面模板中的静态字符串 abc 换成动态绑定的 {{ abc }},那么整棵树都不会被提升。
虽然包含动态绑定的节点本身不会被提升,但是该动态节点上任然可能存在纯静态的属性,例如:

<div><p foo="bar a=b">{{ text }}</p>
</div>

在上面这段模板中,p 标签存在动态绑定的文本内容,因此整个节点都不会被静态提升。但该节点的所有 props 都是静态的,因此在最终生成渲染函数时,我们可以将纯静态的 props 提升到渲染函数之 外,如下面的代码所示:

// 静态提升的 props 对象
const hoistProp = { foo: 'bar', a: 'b' }function render(ctx) {return (openBlock(), createBlock('div', null, [createVNode('p', hoistProp, ctx.text)]))
}

这样可以减少创建虚拟DOM产生的开销以及内存占用。

预字符串化

基于静态提升,我们还可以进一步曹勇预字符串化的优化手段。预字符串是基于静态提升的一种优化策略。静态提升的虚拟节点或虚拟节点树本身是静态的,那么就能将其预字符串化:

<div><p></p><p></p>// ... 20 个 p 标签<p></p>
</div>

假设上面的模板中包含大量连续纯静态的标签节点,当采用了静 态提升优化策略时,其编译后的代码如下:

cosnt hoist1 = createVNode('p', null, null, PatchFlags.HOISTED)cosnt hoist2 = createVNode('p', null, null, PatchFlags.HOISTED)// ... 20 个 hoistx 变量cosnt hoist20 = createVNode('p', null, null, PatchFlags.HOISTED)render() {return (openBlock(), createBlock('div', null, [hoist1, hoist2, /* ...20 个变量 */, hoist20]))
}

预字符串化能够将这些静态节点序列化为字符串,并生成一个 Static 类型的 VNode:

const hoistStatic = createStaticVNode('<p></p><p></p><p></p>...20 个...<p></p>')render() {return (openBlock(), createBlock('div', null, [hoistStatic]))
}

这样有很多的好处:

  • 大块的静态内容可以通过 innerHTML 进行设置,在性能上具有 一定优势。
  • 减少创建虚拟节点产生的性能开销。
  • 减少内存占用。

缓存内联事件处理函数

提到优化,就不得不提对内联事件处理函数的缓存。缓存内联事 件处理函数可以避免不必要的更新。假设模板内容如下:

<Comp @change="a + b" />

上面这段模板展示的是一个绑定了 change 事件的组件,并且为 change 事件绑定的事件处理程序是一个内联语句。对于这样的模板, 编译器会为其创建一个内联事件处理函数,如下面的代码所示:

 function render(ctx) {return h(Comp, {// 内联事件处理函数onChange: () => (ctx.a + ctx.b)})}

很显然,每次重新渲染时(即 render 函数重新执行时),都会 为 Comp 组件创建一个全新的 props 对象。同时,props 对象中 onChange 属性的值也会是全新的函数。这会导致渲染器对 Comp 组 件进行更新,造成额外的性能开销。
为了避免这类无用的更新,我们 需要对内联事件处理函数进行缓存,如下面的代码所示:

function render(ctx, cache) { return h(Comp, { // 将内联事件处理函数缓存到 cache 数组中 onChange: cache[0] || (cache[0] = ($event) => (ctx.a + ctx.b)) }) } 

渲染函数的第二个参数是一个数组 cache,该数组来自组件实 例,我们可以把内联事件处理函数添加到 cache 数组中。这样,当渲染函数重新执行并创建新的虚拟 DOM 树时,会优先读取缓存中的事件处理函数。这样,无论执行多少次渲染函数,props 对象中 onChange 属性的值始终不变,于是就不会触发 Comp 组件更新了。

SSR优化

未完待续·······

源码体积

相比Vue2,Vue3整体体积变小了,除了移出一些不常用的API,再重要的是Tree shanking
任何一个函数,如ref、reavtived、computed等,仅仅在用到的时候才打包,没用到的模块都被摇掉,打包的整体体积变小

import { computed, defineComponent, ref } from 'vue';
export default defineComponent({setup(props, context) {const age = ref(18)let state = reactive({name: 'test'})const readOnlyAge = computed(() => age.value++) // 19return {age,state,readOnlyAge}}
});

响应式系统

vue2中采用 defineProperty来劫持整个对象,然后进行深度遍历所有属性,给每个属性添加getter和setter,实现响应式
vue3采用proxy重写了响应式系统,因为proxy可以对整个对象进行监听,所以不需要深度遍历

  • 可以监听动态属性的添加
  • 可以监听到数组的索引和数组length属性
  • 可以监听删除属性

这篇关于vue3性能提升主要通过哪几方面?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/870800

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

便携式气象仪器的主要特点

TH-BQX9】便携式气象仪器,也称为便携式气象仪或便携式自动气象站,是一款高度集成、低功耗、可快速安装、便于野外监测使用的高精度自动气象观测设备。以下是关于便携式气象仪器的详细介绍:   主要特点   高精度与多功能:便携式气象仪器能够采集多种气象参数,包括但不限于风速、风向、温度、湿度、气压等,部分高级型号还能监测雨量和辐射等。数据采集与存储:配备微电脑气象数据采集仪,具有实时时钟、数据存

黑神话,XSKY 星飞全闪单卷性能突破310万

当下,云计算仍然是企业主要的基础架构,随着关键业务的逐步虚拟化和云化,对于块存储的性能要求也日益提高。企业对于低延迟、高稳定性的存储解决方案的需求日益迫切。为了满足这些日益增长的 IO 密集型应用场景,众多云服务提供商正在不断推陈出新,推出具有更低时延和更高 IOPS 性能的云硬盘产品。 8 月 22 日 2024 DTCC 大会上(第十五届中国数据库技术大会),XSKY星辰天合正式公布了基于星

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能