再探Vue3响应式系统

news2025/1/16 18:54:17

欲看懂这一篇还是建议先看上一篇,这一篇我们继续往下走

一、嵌套问题

🖖先看背景

  • 在这段代码里面,question1里面嵌套了question2,所以question1的执行会导致question2的执行
let temp1 ,temp2;
function question1() {console.log('question1执行')effect(question2)temp1 = proxy.foo
}
function question2() {console.log('question2执行')temp2 = proxy.bar
}
effect(question1) 
  • 由于question1中使用了proxy.fooquestion2使用了proxy.bar,那么我们所希望的结构应该是这样
//理想情况
data|___foo |__question1|___bar|__question2 
  • 但事实却是:初始化的时候触发question1 , question1内再触发一次question2,当我们修改proxy.foo后本应该再次触发question1再触发question2,但是却只触发了question2

🖖分析原因

  • 我们只用了一个activeEffect来存储副作用函数,但是这样当副作用函数嵌套的时候,内层的副作用函数会覆盖activeEffect的值
  • 所以即使proxy.foo是在外层副作用函数question1读取的,但是它收集到的副作用函数也是内层的question2,也就解释了为什么上面只执行question2而不执行question1
//实际情况
data|___foo |__question2|___bar|__question2 

🖖解决方案

  • 涉及到有序的多个数据存储,嵌套函数就是最里层的最后调用,也最先执行完毕,就纯纯符合栈结构
  • 那么我们可以选择使用一个effectStack来存储副作用函数,当副作用函数执行的时候,让它压入栈,当它执行完毕出栈
  • 所以我们一共增加了四步* 新增一个effectStack栈* 在fn()执行前,将effectFn推进栈* 在fn()执行后,将effectFn推出栈* 将activerEffect重新复制为栈顶元素
//用一个全局变量存储被注册的副作用函数
let activeEffect; 
const effectStack = []//新增一个栈
function effect(fn) {const effectFn = () => {cleanup(effectFn);//当effectFn执行的时候,把它设置为当前激活的副作用函数activeEffect = effectFn;effectStack.push(effectFn); //新增fn();effectStack.pop(); //新增activeEffect = effectStack[effectStack.length-1];//新增}effectFn.deps = [];effectFn();
} 

🖖测试

  • 依旧还是上面的修改proxy.foo的值: proxy.foo = '123'
  • 现在就达到预期情况了,执行了question1后再执行了question2

二、递归循环问题

🤚先看背景

当你解决了上面的问题兴致勃勃测试数据的时候,你输入了proxy.bar++,打开控制台一看,坏了,发现它竟然递归调用了导致了栈溢出报错

🤚分析原因

  • proxy.bar++是一个自增的操作,那么它就会先获取值,再设置值
  • 当我们获取值的时候,就会触发track函数,那么该函数就会加入list,当我们+1后设置值的时候,又会触发trigger函数,把list的事件取出执行,它本身都还没有执行完,就又让调用它,那么就是无限递归调用自己,导致如上图所示的栈溢出
  • 本质上就是在同一个函数先获取值,再设置值,就会导致目前这个代码进入循环状态
  • 可以证实一下我们的想法,即不使用自增,而是先获取proxy.bar的值,再设置值,同样报出了错误;但是如果是先设置值,再获取值即无问题
function question2() {console.log('question2执行')temp2 = proxy.bar;proxy.bar = '123'
} 

🤚解决方案

  • 既然我们上述提到了是在同一函数中进行这种操作才有问题,那么我们自然是希望,如果在赋值的时候,发现是触发的副作用函数刚好是当前执行函数的时候就不执行
  • 触发的副作用函数我们可以在forEach循环里面寻找
  • 当前执行函数刚好就是activerEffect
  • 那么就说明我们的effectsToRun不能全盘照收从list取出来的副作用集合,而是需要由一个剔除操作
function trigger(target,key) {const desMap = list.get(target);if(!desMap) return;//从desMap中取中存放副作用事件的Set结构const effects = desMap.get(key);//不要全盘接受const effectsToRun = new Set();//剔除操作effects && effects.forEach(effectFn => {//如果不相等就加进去,如果相等就不处理if(effectFn !== activeEffect) {effectsToRun.add(effectFn)}})effectsToRun.forEach(effectFn=> effectFn());
} 

🤚测试

  • 我们直接拿上面分析原因的那段代码进行测试,发现现在已经解决了递归调用问题,即正常调用

三、扩充调度

🤚背景

  • 当我们问题解决后,自然就想着能不能给他加上一些功能
  • 例如,能不能加上一个调度器,可以用它来控制副作用函数的执行顺序,甚至控制该副作用函数的调用次数

🤚实现方案

  • 要让它可调度,其实就是将它的执行交给我们调度函数去解决,那么只需要走三步:* 传入* 挂载* 使用
  • 传入:我们选择以options作为形参,赋初始值{}options内可以放入scheduler(调度函数)

为什么不是直接传入scheduler呢:因为这个函数不会只有调度这个功能配置功能,所以我们希望传入的结构应该是

//options对象
{
//调度函数
scheduler(fn) { }
} 
  • 挂载:人家都传入来了,你总得用吧,难不成丢那里自己就会调用了(Bushi
function effect(fn, options = {} ) {
 .......//这里都不用修改effectFn.deps = [];//看这里看这里,把它挂载上去即可effectFn.options = options;effectFn();
} 
  • 使用:首先options中可能有传入scheduler,也可能没有传入,那么我们首先就要给它加个判断,其次调度函数影响的是调用过程,那么我们就先去找到trigger函数,再去找到调用它的地方,也就是在forEach那个遍历调用的地方,然后把副作用函数的执行交给调换函数去处理
 // effectsToRun.forEach(effectFn=> effectFn())----旧代码,我们不用了//使用schedulereffectsToRun.forEach(effectFn => {//判断if(effectFn.options.scheduler) {//有则使用effectFn.options.scheduler(effectFn)} else {//没有则则直接调用effectFn()}}); 

🤚测试

  • 比如我希望这个副作用函数能够一秒钟执行一次* 在scheduler里面使用了setInterval每隔一秒就去执行show函数
function show() {document.body.innerText = `现在的数字为${proxy.num}`proxy.num++;
}
effect(show,{scheduler(fn) {setInterval(fn,1000)}
})
proxy.num++ 
  • 结果如图

四、继续扩展

再比如我期望我能够去控制它的执行次数,比如说Vue.js连续多次修改响应式数据但是只触发一次更新的效果

👋实现方案

  • 先定义一个jobQueue集合,用来存放不重复的副作用函数
  • 通过isFlushing(表示是否正在刷新),控制是否是否去执行jobQueue内的任务
  • 通过p.then将函数添加到微任务队列,在微任务队列中遍历执行jobQueue内的任务,执行完将isFlushing置为false
const jobQueue = new Set();
const p = Promise.resolve();
let isFlushing = false;
function flushJob() {if(isFlushing) return;isFlushing = true;p.then(()=>{jobQueue.forEach(job=>job())}).finally(()=>{isFlushing = false})
} 

👋使用

  • 副作用函数为dosomething,打印proxy.num
  • 传入调度函数
  • proxy.num递增十次
function doSomething() {console.log(proxy.num);
}
effect(doSomething,{scheduler(fn) {jobQueue.add(fn);flushJob()}
})
for(let i = 0;i<10;i++) {proxy.num++;
} 
  • 只打印出了

👋分析过程

  • 每一次proxy.num递增,就会进去trigger函数里面找到scheduler,然后jobQueue把该函数推入集合;由于我们jobQueue设置的Set结构,所以你无论推进入同一副作用函数doSomething几次其实里面都只有一个* 图为截取部分过程中打印的jobQueue
  • 推进去之后就调用flushJob函数,一旦它开始执行,isFulshing标志就会被设置为true,也就是说无论你调用多少次,在一个周期内都只会执行一次,实际上它在一个事件循环中只会执行一次* 图为过程中打印isFulshing,可以知道只有第一次是可以进入执行的,后面都被return回来了
  • 当微任务队列开始执行的时候,就会拿出jobQueue中的函数依次执行,但是我们前面已经说了只有一个函数,辣就是说只去执行一次,此时proxy.num的值已经递增到11了,所以就打印出了11

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于java+springmvc+mybatis+vue+mysql的电子资源管理系统

项目介绍 随着互联网技术的高速发展&#xff0c;人们生活的各方面都受到互联网技术的影响。现在人们可以通过互联网技术就能实现不出家门就可以通过网络进行系统管理&#xff0c;交易等&#xff0c;而且过程简单、快捷。同样的&#xff0c;在人们的工作生活中&#xff0c;也就…

AI加速自动驾驶进程,景联文科技提供数据采集标注服务

“当前&#xff0c;路面上搭载各级别自动驾驶系统的车辆数量逐渐增多。对自动驾驶领域头部企业来说&#xff0c;为了保持自身的竞争优势并加速自动驾驶应用安全落地进程&#xff0c;需要依靠大量的高质量标注数据来训练优化自动驾驶相关算法模型。数据作为AI技术的底层基础&…

备战2023蓝桥国赛-饼干

题目描述&#xff1a; 解析&#xff1a; 这道题我想了很多种解决方法&#xff0c;但无一例外都失败了&#xff0c;实在是按照常规线性DP的思路真的想不出来。 看了题解之后才知道它是分为三步解决这个问题的&#xff1a; 第一步&#xff1a;缩小最优解的范围 先用贪心将最优解…

如何准备好2023年的USACO?

目录 1. 注册 2. 刷题 3. 备考 4. 考试流程/介绍 5. 铜组例题 1. 注册 先进入usaco的官网&#xff0c;主页的右边会有注册的选项&#xff0c;点击Register for New Account。会让你填你自己的用户名&#xff0c;邮箱&#xff0c;实名&#xff0c;毕业的年份&#xff0c;还…

世界杯数据分析

国际足联世界杯&#xff08;FIFA World Cup&#xff09; 文章目录前言一、历届世界杯数据分析&#xff08;一&#xff09;建表&#xff08;二&#xff09;导入数据&#xff08;三&#xff09;数据分析1. 全勤巴西2. 夺冠排名3. 扩军历史4. 进球之最二、本届世界杯数据分析&…

【Java寒假打卡】Java基础-数据类型以及转换

【Java寒假打卡】Java基础-数据类型以及转换一、关键字二、强制转换三、常量四、隐式转换的问题五、变量六、程序输入七、标识符八、类型转换一、关键字 含义&#xff1a;java 中被赋予特定含义和特点的单词 二、强制转换 数据范围大变量赋给一个数据范围小的变量int 到byte…

android绘制弧形背景

先看一下效果&#xff1a; 在drawable中写shape.xml文件 <?xml version"1.0" encoding"utf-8"?> <layer-list xmlns:android"http://schemas.android.com/apk/res/android"><item><shape><solid android:color&quo…

Hadoop学习----Hadoop介绍

Hadoop介绍 Hadoop是Apache软件基金会的一款开源软件。底层是由java语言实现。 功能&#xff1a;允许用户使用简单的编程模型实现跨机器集群对海量数据进行分布式计算处理。 Hadoop核心组件&#xff1a; Hadoop HDFS&#xff08;分布式文件存储系统&#xff09;&#xff1a;解…

大道至简——工具类产品的几个思考方向

因为之前没有接触过摹客的产品&#xff0c;对于一个经常使用Axure的产品经理&#xff0c;仅仅通过两周摹客的使用体验写出的体验文档确实不够严谨&#xff0c;所以以下除了表层的几点用户体验&#xff0c;重点还是谈一下对工具类产品发展方向的思考。 体验篇 一款产品的诞生肯…

4.2 YOLOv3算法

文章目录一、林业病虫害数据集和数据预处理方法介绍1.1 读取AI识虫数据集标注信息1.2 数据读取和预处理1.2.1 数据读取1.2.2 数据预处理**随机改变亮暗、对比度和颜色等****随机填充****随机裁剪****随机缩放****随机翻转****随机打乱真实框排列顺序****图像增广方法汇总**1.2.…

Oracle云服务器安全配置

在Oracle云上申请了一个免费云空间&#xff0c;据说是永久免费&#xff0c;这里记录一下安全配置问题。 一、访问Oracle云服务器 1、云服务器申请 如何在oracle注册申请云服务可自行搜索一下&#xff0c;有很多文章介绍。 甲骨文Oracle云服务器详细申请教程步骤及注意事项 …

如何计算香港服务器公网带宽的实际下载速度?

如何计算香港服务器公网带宽的实际下载速度?下面分享香港服务器带宽实际下载速度对照表及计算方法&#xff1a; 香港服务器带宽实际下载速度计算方法 香港服务器以1Mbps公网带宽为例&#xff0c;香港服务器1M带宽实际下载速度峰值128KB/S&#xff0c;为什么不是1M/S&#xff0…

电脑重装系统后重启电脑黑屏是怎么回事

​电脑重装系统后重启电脑黑屏怎么回事&#xff1f;最近有用户反映自己在重装电脑系统后&#xff0c;开机出现了黑屏的现象&#xff0c;检查后发现硬件设备没有故障&#xff0c;那么是怎么回事呢&#xff1f;有可能是显示器不支持该显卡显示的分辨率或者是重装系统后的显卡驱动…

结构建模设计——Solidworks软件之使用钣金折弯功能做一个带折弯固定口的铝合金面板

【系列专栏】&#xff1a;博主结合工作实践输出的&#xff0c;解决实际问题的专栏&#xff0c;朋友们看过来&#xff01; 《QT开发实战》 《嵌入式通用开发实战》 《从0到1学习嵌入式Linux开发》 《Android开发实战》 《实用硬件方案设计》 长期持续带来更多案例与技术文章分享…

31岁才转行程序员,目前34了,我的经历和一些感受

按惯例&#xff0c;先说下我基本情况。我是85年的&#xff0c;计算机专业普通本科毕业。在一个二线城市&#xff0c;毕业后因为自身能力问题、认知水平问题&#xff0c;再加上运气不好&#xff0c;换过多份工作&#xff0c;每份工作都干不长。导致我30多岁时&#xff0c;还一事…

背包问题:蜣螂优化算法(Dung beetle optimizer,DBO)求解背包问题(Knapsack Problem,KP)提供Matlab代码

一、背包问题 1.1背包问题描述 背包问题(Knapsack Problem&#xff0c;KP)是一种重要的组合优化问题&#xff0c;在生活的许多领域都有着十分广泛的应用。背包问题可以描述为&#xff1a;给定一个背包和n种物品&#xff0c;其中&#xff0c;背包的容量为VVV &#xff0c;第i …

C++11标准模板(STL)- 算法(std::min_element)

定义于头文件 <algorithm> 算法库提供大量用途的函数&#xff08;例如查找、排序、计数、操作&#xff09;&#xff0c;它们在元素范围上操作。注意范围定义为 [first, last) &#xff0c;其中 last 指代要查询或修改的最后元素的后一个元素。 返回范围内的最小元素 st…

[附源码]Python计算机毕业设计个人资金账户管理Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

PMP考试有用吗?

有用&#xff0c;对于项目管理岗位来说&#xff0c;PMP 就是一个最基础的必备的证书&#xff0c;你说含金量有多高不见得&#xff0c;但是没有这个证书找工作可能会受阻。 下面这两种情况对 PMP 的需求是硬性要求&#xff0c;很现实&#xff0c;有证才能获得资格&#xff0c;没…

图算法在风控场景的应用

​导读&#xff1a;本文将分享图算法在风控中的应用。 今天的介绍会围绕下面四点展开&#xff1a; 图算法和风控简介 图算法在风控的演化 相应平台的心得 展望未来 分享嘉宾&#xff5c;汪浩然 互联网行业资深风控和图计算专家 编辑整理&#xff5c;戴杰 永辉超市 出品社…