首页 > 系统相关 >揭秘!Vue3.5响应式重构如何让内存占用减少56%

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

时间:2024-11-13 17:18:46浏览次数:3  
标签:重构 依赖 Sub 订阅 Dep 响应 Link Vue3.5 56%

前言

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

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

因为在之前的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计算属性是惰性计算了,这些优化同样也优化了内存的使用。

文章转载自:前端欧阳

原文链接:https://www.cnblogs.com/heavenYJJ/p/18542806

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

标签:重构,依赖,Sub,订阅,Dep,响应,Link,Vue3.5,56%
From: https://blog.csdn.net/sdgfafg_25/article/details/143742983

相关文章

  • [开题报告]基于javaweb的宠物医院平台dz56j9计算机毕业设计源码、研究背景、意义、目
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着人们生活水平的提高和城市化进程的加速,宠物已成为许多家庭的重要成员。宠物数量的增加带动了宠物相关产业的发展,其中宠物医院作为宠物健康保障的......
  • P10856 【MX-X2-T5】「Cfz Round 4」Xor-Forces题解
    题意:给定一个长度为\(n=2^k\)的数组\(a\),下标从\(0\)开始,维护\(m\)次操作:给定\(x\),设数列\(a'\)满足\(a'_i=a_{i\oplusx}\),将\(a\)修改为\(a'\)。其中\(\oplus\)表示按位异或运算。给定\(l,r\),查询\(a\)的下标在\(l,r\)之间的子数组有多少颜色段。不保......
  • Kruskal 重构树学习笔记+杂题
    图论系列:前言:相关题单:戳我一.最小瓶颈路唉,前面4个题单里其实有不少题是最小瓶颈路的做法啊。讲解摘自wiki。1.定义无向图\(G\)中\(x\)到\(y\)的最小瓶颈路是这样的一类简单路径,满足这条路径上的最大的边权在所有\(x\)到\(y\)的简单路径中是最小的。(对于下面这张......
  • 揭秘!Vue3.5响应式重构如何让内存占用减少56%
    前言Vue3.5版本又将响应式给重构了,重构后的响应式系统主要有两部分组成:双向链表和版本计数。我们在前两篇文章中我们已经讲过了双向链表和版本计数,这篇文章我们来讲讲为什么这次重构能够让内存占用减少56%。欧阳年底也要毕业了,加入欧阳的面试交流群(分享内推信息)、高质量vue......
  • AtCoder Beginner Contest 356 - VP记录
    A-SubsegmentReverse点击查看代码#include<cstdio>#include<numeric>#include<algorithm>usingnamespacestd;constintN=105;intn,a[N],l,r;intmain(){ scanf("%d%d%d",&n,&l,&r); iota(a+1,a+n+1,1); reverse(a+l,......
  • ARL356-ASEMI车用整流二极管ARL356
    编辑:llARL356-ASEMI车用整流二极管ARL356型号:ARL356品牌:ASEMI封装:BUTTON特性:车用整流二极管正向电流:35A反向耐压:600V恢复时间:ns引脚数量:2芯片个数:2芯片尺寸:MIL浪涌电流:500A漏电流:10ua工作温度:-65℃~175℃包装方式:500/盘;5000/箱备受欢迎的ARL356-ASEMI车用整流二极......
  • 开发更便利!迅为RK3568/RK3588 定制分区镜像发布
             迅为iTOP-3588开发板采用瑞芯微RK3588处理器,是全新一代AloT高端应用芯片,采用8nmLP制程,搭载八核64位CPU(四核Cortex-A76+四核Cortex-A55架构),集成MaliG610MP4四核GPU,内置AI加速器NPU,算力达6Tops,集成独立的8K视频硬件编码器和硬件解码器,提供了许多功能强大的......
  • 56. 合并区间
    题目链接解题思路合并区间,肯定要按照第一维度排序。然后依次处理每个区间。假设现在来到i区间[a,b],i之前的区间已经处理好,并且与i区间不重叠。i+1的区间是[c,d],因为已经按照第一维度排序,所以能够得到a>=c,那么,b和c的关系如何?b<c:说明i区间与i+1区间不重叠,直接得到......
  • DHCP移植到瑞芯微RK356x平台
    dhcpd交叉编译1.简介项目中需要在RK3566上配置DHCP服务器,需要移植DHCP编译环境:Ubuntu20.04DHCP版本:v4.4.32.zlib移植dhcp交叉编译依赖libz.sozlib是一个广泛使用的开源数据压缩库,提供了数据压缩和解压缩的功能下载zlib源码,选择使用1.3.1版本,下载地址https://......
  • P4156 论战捆竹竿 题解
    论战捆竹竿题意:给定字符串\(s\),计数"串\(t\)的长度"可能的种数有多少种,使得\(t\)能被\(s\)作为印章印出来,且\(|t|\lew\)。\(n=|s|\le5\times10^5\),\(n\lew\le10^{18}\)。第一步:求出\(s\)的周期\(\{a_1\sima_m\}\),包含\(n\)(\(a_m=n\))。转化为求\(\suma_ib......