揭秘!Vue3.5响应式重构如何让内存占用减少56%

news/2024/11/14 12:22:13/文章来源:https://www.cnblogs.com/heavenYJJ/p/18542806

前言

Vue3.5版本又将响应式给重构了,重构后的响应式系统主要有两部分组成: 双向链表和 版本计数。我们在前两篇文章中我们已经讲过了 双向链表和 版本计数,这篇文章我们来讲讲为什么这次重构能够让内存占用减少56%。

欧阳年底也要毕业了,加入欧阳的面试交流群(分享内推信息)、高质量vue源码交流群

为什么说“又”将响应式重构了

因为在之前的Vue3.4版本中刚刚将响应式给重构了,这次响应式重构是vscode插件Vue-Official(原名Volar)的作者Johnson Chu搞的。

3.4版本的重构优化了很多东西,最直观的就是:computed计算属性的值没有变化,另外一个watch又监听了这个computed的值。在3.4以前还是会触发watch的回调,经过3.4的优化后就不会触发了。

在3.5版本以前,Vue的响应式系统中有两个角色:Sub订阅者和Dep依赖。

Sub订阅者:主要有watchEffect、watch、render函数、computed等。

Dep依赖:主要有ref、reactive、computed等响应式变量。

他们两之间是相互依赖的关系,如下图:
old

Dep依赖(比如ref响应式变量)可以通过dep属性访问到Sub订阅者(比如computed计算属性),就知道了到底有哪些订阅者依赖自己,当自己的值改变后就能去通知订阅者。

同样Sub订阅者(比如computed计算属性)可以通过deps属性访问到Dep依赖(比如ref响应式变量),当Sub订阅者不再依赖某个变量时就可以通过这个关系去访问到这个Dep依赖。然后把自己从不再依赖的变量的Sub订阅者集合中去掉,这样当这个响应式变量改变后就不会通知到不再订阅到他的Sub订阅者了。

我们来看个例子,代码如下:

<template><p>{{ doubleCount }}</p><button @click="flag = !flag">切换flag</button>
</template><script setup>
import { computed, ref } from "vue";
const count1 = ref(1);
const count2 = ref(10);
const flag = ref(true);const doubleCount = computed(() => {console.log("computed");if (flag.value) {return count1.value * 2;} else {return count2.value * 2;}
});
</script>

flag的值为true时计算属性doubleCount其实只依赖响应式变量flagcount1,当flag的值切换为false时,计算属性应该变成依赖变量flagcount2

就上面这个更新Sub订阅者依赖的逻辑,Vue其实重构了很多次。在早期的Vue3版本中是直接清空Sub订阅者所依赖的响应式变量,然后再重新执行计算属性doubleCount时再去将新的响应式变量进行收集。很明显这个版本内存的使用就非常浪费了。

在最新的Vue3.4版本重构后的响应式系统中会在执行计算属性之前利用_trackId_depsLength字段进行标记,在重新执行计算属性时进行依赖收集就可以利用_trackId_depsLength字段判断出Dep依赖是否能够复用,并且执行完计算属性的回调函数后同样利用_trackId_depsLength字段就可以将不再依赖的Dep依赖给移除掉。

上面这个方案看着很完美,但是他的核心是依赖计算属性中所依赖的变量顺序不变,如果顺序变了,那么依然还是不能够复用的,同样会对浪费内存。(PS:这一段3.4版本响应式看不懂没关系,因为他已经是过去式了)

内存优化主要原因:复用Link节点

在Vue3.5版本中那个最了解Vue的男人出手了,使用双向链表版本计数将响应式系统再次给重构了。说实话这次重构后让读响应式源码的门槛变得更高了,但是收益特别明显,最主要是通过复用Link节点去实现减少内存的使用。

还是上面的那个例子,对应新的响应式模型如下图:
reactive

在新的响应式模型中Sub订阅者Dep依赖之间不再有直接的关联关系了,而是通过中间的Link节点作为桥梁去关联。

在前一节中我们讲过了,3.5以前Sub订阅者中有属性会去存依赖的Dep依赖Dep依赖中有属性去存依赖他的Sub订阅者,所以导致当Sub订阅者依赖的变量需要更新时就无法做到完全的复用,内存就会浪费。

如果下面的内容你看不懂,这不是你理解力有问题,原因是你对双向链表不熟悉,可以先看看我之前的 双向链表文章。

在3.5新的响应式模型中,X轴是Dep依赖,Y轴是Sub订阅者Link节点是作为坐标轴上面的点。每一组Dep依赖Sub订阅者都会对应一个Link节点,并且可以通过这个Link节点直接访问到Dep依赖Sub订阅者

在Y轴上面找一个点(比如Sub1也就是计算属性doubleCount),横向出发就可以找到Sub1订阅者所依赖的所有响应式变量。因为横向的这些Link节点是一个双向链表,并且可以通过某一个Link节点直接访问到他的Dep依赖。

flag的值切换为false后,订阅者Sub1所依赖的响应式变量就从flag+count1变成flag+count2。这时我们需要做的事情就很简单了,新建一个Link3节点,可以直接访问到Sub1Dep3。然后将Link1中原本指向Link2的指针改为指向Link3,同时让Link3的指针也指向Link1。并且将Link2指向Link1的指针改为指向,由于Dep2现在不被任何订阅者所依赖了,所以将Link2原本指向Dep2的指针也改为指向空,同样将Dep2指向Link2的指针也指向空。

上面的一顿操作,除了必要的初始化一个Link3之外我们一直都是在进行指针的操作,并不像以前的响应式一样去增加Sub订阅者依赖或者减少依赖,这是非常高效的方式。

flag的值切换为false后,新的响应式模型图如下:
reactive2

从上图中可以看到Link2已经彻底从双向链表中移除了,并且整个过程中我们都是在操作指针的指向,所以Link1也一直都是复用的。

V8在进行垃圾回收的时候发现Link2不再被任何变量所使用,就可以认为Link2是一个可以被回收的变量,就会将其直接回收释放内存。

Link节点复用以及让不再使用的Link节点尽快的被回收进而释放内存,就是这次响应式重构减少56%内存占用的主要原因。

其他优化

有了双向链表后依赖触发也变得更加清晰了,当某个响应式变量改变后,只需要遍历Dep依赖(纵向)的Link节点组成的双向链表,然后通过这些Link节点直接访问到对应的Sub订阅者,触发其依赖。

基于此Sub订阅者的触发就是一个线性的过程,所以就可以实现将需要触发的Sub订阅者串起来组成了一个Sub订阅者组成的队列。等需要触发的订阅者收集完了后,再去进行触发Sub订阅者,避免同一个订阅者被触发多次。

依赖触发相比之前也变得更加简单了,性能以及内存也有所提升。

最后就是因为有了双向链表版本计数的加持后,computed计算属性变得更加聪明,现在是惰性计算了。computed计算属性只有等有人使用他(比如在template中使用计算属性doubleCount)后才会去执行计算属性中的回调函数,以及3.4版本中就已经实现的如果计算属性值没有变化,另外一个watch又监听了这个computed的值,此时这个watch不会被触发。关于这个可以看我之前的版本计数文章。

总结

Vue3.5响应式重构主要是通过双向链表版本计数实现的,优化后内存占用减少了56%。主要原因是:在新的响应式系统中多了一个Link节点用于链接Sub订阅者Dep依赖,更新Sub订阅者依赖只是进行指针的变换,并且还能够复用Link节点以及将不再使用的Link节点给孤立出来便于V8更快的将这个Link节点给回收。此外还有Sub订阅者的触发也变得更加简单,以及现在是computed计算属性是惰性计算了,这些优化同样也优化了内存的使用。

关注公众号:【前端欧阳】,给自己一个进阶vue的机会

另外欧阳写了一本开源电子书vue3编译原理揭秘,看完这本书可以让你对vue编译的认知有质的提升。这本书初、中级前端能看懂,完全免费,只求一个star。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/832784.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

11.14

[实验任务一]:计算机组装 使用建造者模式,完成下述任务:计算机组装工厂可以将CPU、内存、硬盘、主机等硬件设备组装在一起构成计算机,计算机的类型可以是笔记本,也可以是台式机。 实验要求:画出对应的类图;提交源代码;// Computer.java package builder;public class C…

网站安全狗修改远程端口,如何调整网站防火墙设置以增强安全性

网站安全狗是一款流行的网站防护软件,通过修改远程管理端口可以增加黑客攻击的难度。操作步骤如下:登录安全狗控制台:首先登录到安全狗的管理后台。 找到端口设置:在“设置”或“高级设置”中找到与远程管理相关的选项。 修改端口:选择一个不常用的端口号进行更改,并保存…

修改网站用户名,如何更改用户的登录名

修改网站用户名通常需要通过后台管理系统进行:登录管理后台:使用管理员账号登录网站管理后台。 进入用户管理:导航至“用户管理”或“会员管理”模块。 选择用户:找到需要修改的用户。 编辑用户名:在“用户名”或“登录名”字段中,输入新的用户名。 保存更改:确认无误后…

47DR工程 - 48DR工程

47DR工程 -> 48DR改变工程芯片型号 工程设置更新所有IP 使用Report IP更新用TCL命令更新所有IP upgrade_ip [get_ips]报警告,不影响正常功能: WARNING: [IP_Flow 19-2248] Failed to load user IP repository c:/Users/GAOCHEN/Desktop/ACTAN/rfsoc_project_6U/ip_repo/pl…

NOIP2024 前集训:多校A层冲刺NOIP2024模拟赛20

前言rk 历程:\(11\to 9\to 8\to 7\),原因是部分人的 T1 假做法被卡(感觉目前这些不二分也不 DP(特指 Hangry 这样的 \(O(n^4)\) DP)都能卡)。 T2 建图建错了(没判谁是父亲也没建双向边)暴力死了,挂了 \(24\)。 我是唯一一个打了 T3 启发正解部分分但没有写出正解的人……

本人联系方式

点击直达 :https://t.me/ios888企鹅号码:616804217微信添加:616804217

ServiceMesh 4:实现流量染色和分级发布

★ ServiceMesh系列 1 什么是流量染色 在复杂的生产场景中,经常会有同一个服务中,存在多个版本长期共存的需求。为了让不同的用户在不一样的版本中使用,就需要对用户的请求进行采样和染色,打上不同的标识。 这样的目的有几个:支撑分级发布,避免全量发布时可能遇到的大规模…

SonicWall NSv 系列虚拟防火墙 SonicOSX 7.0 下载

SonicWall NSv 系列虚拟防火墙 SonicOSX 7.0 下载SonicWall NSv 系列虚拟防火墙 SonicOSX 7.0 SonicWall NSv SonicOSX 7.0 for ESXi 请访问原文链接:https://sysin.cn/blog/sonicwall-nsv/ 查看最新版。原创作品,转载请保留出处。 作者主页:sysin.org在获得物理防火墙所有安…

读数据质量管理:数据可靠性与数据质量问题解决之道02数据湖仓

数据湖仓1. 组装 1.1. 对于任何数据从业者来说,解决生产过程中的数据质量问题都是一项关键技能,但只要有适当的系统和流程,就基本可以防止数据宕机 1.2. 数据在管道的任何阶段都可能会受到操作数量、编程甚至数据相关性的影响,也许只需一次模式更改或代码推送,就会让下游报…

.NET 9正式发布,亮点是.NET Aspire和AI

Microsoft 今天正式发布了 .NET 9,这是迄今为止最高效、最现代、最安全、最智能、性能最高的 .NET 版本。这是来自世界各地的数千名开发人员又一年努力的结果。此新版本包括数千项性能、安全性和功能改进。您将发现整个 .NET 堆栈中从编程语言、开发人员工具和工作负载的全面增…

多校 A 层冲刺 NOIP2024 模拟赛 21

难度 ★★★☆☆多校A层冲刺NOIP2024模拟赛21 T1 送信卒 签到题 答案显然具有单调性,考虑二分答案,然后使用跑 dj check。 时间复杂度 \(O(nm\log (nm)\log (V))\) T2 共轭树图 特殊性质,树形 DP 考虑链怎么做,注意到一个性质,一条链上产生在 \(G\),将 \(G\) 中的边放到原…

APISR:受动漫制作启发的现实世界动漫超分辨率

APISR:受动漫制作启发的现实世界动漫超分辨率虽然现实世界的动漫超分辨率(SR)在SR社区越来越受到关注,但现有的方法仍然采用真实感领域的技术。分析了动漫制作工作,并重新思考了如何为了现实世界的动漫SR而使用它的特点。首先,由于手绘框架的重复使用,视频网络和数据集对…