揭秘!Vue3.5响应式重构如何让内存占用减少56%
前言
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等响应式变量。
他们两之间是相互依赖的关系,如下图:
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
其实只依赖响应式变量flag
和count1
,当flag
的值切换为false时,计算属性应该变成依赖变量flag
和count2
。
就上面这个更新Sub订阅者
依赖的逻辑,Vue其实重构了很多次。在早期的Vue3版本中是直接清空Sub订阅者
所依赖的响应式变量,然后再重新执行计算属性doubleCount
时再去将新的响应式变量进行收集。很明显这个版本内存的使用就非常浪费了。
在最新的Vue3.4版本重构后的响应式系统中会在执行计算属性之前利用_trackId
和_depsLength
字段进行标记,在重新执行计算属性时进行依赖收集就可以利用_trackId
和_depsLength
字段判断出Dep依赖是否能够复用,并且执行完计算属性的回调函数后同样利用_trackId
和_depsLength
字段就可以将不再依赖的Dep依赖给移除掉。
上面这个方案看着很完美,但是他的核心是依赖计算属性中所依赖的变量顺序不变,如果顺序变了,那么依然还是不能够复用的,同样会对浪费内存。(PS:这一段3.4版本响应式看不懂没关系,因为他已经是过去式了)
内存优化主要原因:复用Link节点
在Vue3.5版本中那个最了解Vue的男人出手了,使用双向链表
和版本计数
将响应式系统再次给重构了。说实话这次重构后让读响应式源码的门槛变得更高了,但是收益特别明显,最主要是通过复用Link节点去实现减少内存的使用。
还是上面的那个例子,对应新的响应式模型如下图:
在新的响应式模型中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
节点,可以直接访问到Sub1
和Dep3
。然后将Link1
中原本指向Link2
的指针改为指向Link3
,同时让Link3
的指针也指向Link1
。并且将Link2
指向Link1
的指针改为指向空
,由于Dep2
现在不被任何订阅者所依赖了,所以将Link2
原本指向Dep2
的指针也改为指向空,同样将Dep2
指向Link2
的指针也指向空。
上面的一顿操作,除了必要的初始化一个Link3
之外我们一直都是在进行指针的操作,并不像以前的响应式一样去增加Sub订阅者依赖或者减少依赖,这是非常高效的方式。
当flag
的值切换为false后,新的响应式模型图如下:
从上图中可以看到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。
揭秘!Vue3.5响应式重构如何让内存占用减少56%的更多相关文章
- 简单对比vue2.x与vue3.x响应式及新功能
简单对比vue2.x与vue3.x响应式 对响应方式来讲:Vue3.x 将使用Proxy ,取代Vue2.x 版本的 Object.defineProperty. 为何要将Object.defineP ...
- Vue3.0工程创建 && setup、ref、reactive函数 && Vue3.0响应式实现原理
1 # 一.创建Vue3.0工程 2 # 1.使用vue-cli创建 3 # 官方文档: https://cli.vuejs.org/zh/guide/creating-a-project.html# ...
- Vue3.0响应式原理
Vue3.0的响应式基于Proxy实现.具体代码如下: 1 let targetMap = new WeakMap() 2 let effectStack = [] //存储副作用 3 4 const ...
- Vue3.0 响应式数据原理:ES6 Proxy
Vue3.0 开始用 Proxy 代替 Object.defineProperty了,这篇文章结合实例教你如何使用Proxy 本篇文章同时收录[前端知识点]中,链接直达 阅读本文您将收获 JavaSc ...
- vue2与vue3实现响应式的原理区别和提升
区别: vue2.x: 实现原理: 对象类型:Object.defineProperty()对属性的读取,修改进行拦截(数据劫持): 数组类型:通过重写更新数组的一系列方法来进行拦截(对数组的变更方法 ...
- Vue3.0响应式实现
基于Proxy // 弱引用映射表 es6 防止对象不能被回收 let toProxy = new WeakMap(); // 原对象: 代理过得对象 let toRaw = new WeakMap( ...
- vue3响应式模式设计原理
vue3响应式模式设计原理 为什么要关系vue3的设计原理?了解vue3构建原理,将有助于开发者更快速上手Vue3:同时可以提高Vue调试技能,可以快速定位错误 1.vue3对比vue2 vue2的原 ...
- vue3 第二天vue响应式原理以及ref和reactive区别
前言: 前天我们学了 ref 和 reactive ,提到了响应式数据和 Proxy ,那我们今天就来了解一下,vue3 的响应式 在了解之前,先复习一下之前 vue2 的响应式原理 vue2 的响应 ...
- vue3响应式原理以及ref和reactive区别还有vue2/3生命周期的对比,第二天
前言: 前天我们学了 ref 和 reactive ,提到了响应式数据和 Proxy ,那我们今天就来了解一下,vue3 的响应式 在了解之前,先复习一下之前 vue2 的响应式原理 vue2 的响应 ...
- 前端必读:Vue响应式系统大PK
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://www.sitepoint.com/vue-3-reactivity-system ...
随机推荐
- Windows 10 美化 Mac OSX 实用教程
我前几天给新电脑装上了Windows 10系统,想要美化一下,遇到了很多问题,就出了这篇博客,帮大家踩踩坑. 在开始之前,先提醒大家一句: 美化有风险,玩机需谨慎.为以防万一,请大家在进行任何操作前创 ...
- .net core下使用事件总线
随着微服务的火热,DDD(领域驱动设计模式)思想风起云涌,冲击着整个软件生态系统.其中,事件总线那是必须知道的了,于是我便抱着一个学习DDD的心态搭建了一个博客网站,目前该网站正在建设 ...
- 【Jmeter】之批量处理多接口压力测试
一.需求前提 1.有以下三个步骤: ①创建单据 ②审核单据 ③确认单据 让三个相关接口进行一连串批量请求操作,直到所有批量数据确认单据成功. 二.测试计划 需要说明的是,因为每个接口可能处理的不太一样 ...
- FFmpeg开发笔记(四十九)助您在毕业设计中脱颖而出的几个流行APP
对各高校即将毕业的学子来说,毕业设计算是在大学里的最后一个大作业.特别是软件.计算机.电子等相关专业,毕业设计非常讲究实操,往往要求实现一个实用的.可用的.易用的软件系统或手机APP. 不管是软件还 ...
- C语言浮点数转字符串实现函数
C语言浮点数转字符串可用库函数sprintf,此处为编写的简单函数. 小数部分最多显示六位. pOut:输出字符串缓冲区 f:浮点数值 isize:输出字符串缓冲区大小 char * Funftoa( ...
- .NET 多版本 WinForm 开源控件库 SunnyUI
前言 给大家推荐一款开源的 Winform 控件库,可以帮助我们开发更加美观.漂亮的 WinForm 界面. 项目介绍 SunnyUI.NET 是一个基于 .NET Framework 4.0+..N ...
- SuperMap iServer数据动态更新刷新地图与数据服务
更新:2022年6月27日 SuperMap iServer 11i 底层修改逻辑,增加智能指针.11i版本不需要以下操作即可实现 一.使用背景 有这么一个需求,后端也就通过SuperMap iDes ...
- oh-my-zsh nvm command not found
oh-my-zsh nvm command not found 如果你在使用 oh-my-zsh 并且在终端输入 nvm 命令时提示 "command not found",这可能 ...
- 全网最适合入门的面向对象编程教程:55 Python字符串与序列化-字节序列类型和可变字节字符串
全网最适合入门的面向对象编程教程:55 Python 字符串与序列化-字节序列类型和可变字节字符串 摘要: 在 Python 中,字符编码是将字符映射为字节的过程,而字节序列(bytes)则是存储这些 ...
- Laravel视图共享数据
Laravel视图共享数据 最近在用lavavel过程中想实现公共头部尾部需要的配置数据在所有的页面中都可以使用,便查看了官方文档,在此做一个总结: 一. 修改 ComposerServiceProv ...