Vue.js---嵌套的effect与effect栈
4.3嵌套的effect与effect栈
1、嵌套的effect
effect是可以发生嵌套的
01 effect(function effectFn1() {
02 effect(function effectFn2() { /* ... */ })
03 /* ... */
04 })
有这么一段代码:
01 // 原始数据
02 const data = { foo: true, bar: true }
03 // 代理对象
04 const obj = new Proxy(data, { /* ... */ })
05
06 // 全局变量
07 let temp1, temp2
08
09 // effectFn1 嵌套了 effectFn2
10 effect(function effectFn1() {
11 console.log('effectFn1 执行')
12
13 effect(function effectFn2() {
14 console.log('effectFn2 执行')
15 // 在 effectFn2 中读取 obj.bar 属性
16 temp2 = obj.bar
17 })
18 // 在 effectFn1 中读取 obj.foo 属性
19 temp1 = obj.foo
20 })
我们希望副作用函数与对象属性之间的关系如下:
01 data
02 └── foo
03 └── effectFn1
04 └── bar
05 └── effectFn2
但是当我们发现我们修改foo的值出来的却是:
01 'effectFn1 执行'
02 'effectFn2 执行'
03 'effectFn2 执行'
一共打印三次,前两次分别是副作用函数effectFn1 与 effectFn2 初始执行的打印结果,到这一步是正常的,问题出在第三行打印。我们修改了字段 obj.foo 的值,发现 effectFn1 并没有重新执行,反而使得 effectFn2 重新执行了,这显然不符合预期。
问题出现在哪里呢?
01 // 用一个全局变量存储当前激活的 effect 函数
02 let activeEffect
03 function effect(fn) {
04 const effectFn = () => {
05 cleanup(effectFn)
06 // 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
07 activeEffect = effectFn
08 fn()
09 }
10 // activeEffect.deps 用来存储所有与该副作用函数相关的依赖集合
11 effectFn.deps = []
12 // 执行副作用函数
13 effectFn()
14 }
我们用全局变量 activeEffect 来存储通过 effect函数注册的副作用函数,这意味着同一时刻activeEffect 所存储的副作用函数只能有一个。当副作用函数发生嵌套时,内层副作用函数的执行会覆盖 activeEffect 的值,并且永远不会恢复到原来的值,那么foo应该放入的是effect1也会被effect2所覆盖。
2、effect栈
如何解决???effect栈
在副作用函数执行时,将当前副作 用函数压入栈中,待副作用函数执行完毕后将其从栈中弹出,并始终让 activeEffect 指向栈顶的副作用函数。这样就能做到一个响应式数据只会收集直接读取其值的副作用函数,而不会出现互相影响的情况。
修改代码:
let activeEffect;// effect栈const effectStack = [];function effect (fn) {const effectFn = () => {// 调用clearup函数完成清除工作clearUp(effectFn);activeEffect = effectFn; // 在调用副作用函数之前将副作用函数压入栈中effectStack.push(effectFn);fn();// 执行完之后抛出,把activeEffect还原成原来的值effectStack.pop();//effectStack[effectStack.length - 1] 用于获取栈顶的副作用函数,并将其设置为 activeEffect。activeEffect = effectStack[effectStack.length - 1];}// deps用来存储所有与该副作用函数相关联的依赖集合effectFn.deps = [];// 执行副作用函数effectFn();}
完整代码:
<script setup>let activeEffect;// effect栈const effectStack = [];function effect (fn) {const effectFn = () => {// 调用clearup函数完成清除工作clearUp(effectFn);activeEffect = effectFn; // 在调用副作用函数之前将副作用函数压入栈中effectStack.push(effectFn);fn();// 执行完之后抛出,把activeEffect还原成原来的值effectStack.pop();activeEffect = effectStack[effectStack.length - 1];}// deps用来存储所有与该副作用函数相关联的依赖集合effectFn.deps = [];// 执行副作用函数effectFn();}const bucket = new WeakMap();const data = { text: 'world' }; // 确保所有属性都已定义const obj = new Proxy(data, {get(target, key){track(target , key);return target[key];},set(target, key, newVal){target[key] = newVal;trigger(target , key , newVal);}});// 追踪变化function track(target , key){if(!activeEffect){return;}// 根据tartget取来的depsMap,它是一个map类型let depsMap = bucket.get(target);// 如果不存在if(!depsMap){// 创建一个bucket.set(target, (depsMap = new Map()));}// 根据key取来的deps,它是一个set类型let deps = depsMap.get(key);// 如果不存在if(!deps){// 创建一个depsMap.set(key, (deps = new Set()));}deps.add(activeEffect); // 添加当前活跃的副作用函数activeEffect.deps.push(deps);}
// 触发变化function trigger(target, key, newVal) {const depsMap = bucket.get(target);if (!depsMap) {return;}const effects = depsMap.get(key);const effectsToRun = new Set(effects);effectsToRun.forEach(effectFn => effectFn());// effects && effects.forEach(fn => fn()); // 只触发与键相关的副作用函数}// 清除函数function clearUp (effectFn){// 遍历然后进行删除for(let i = 0 ; i < effectFn.deps.length ; i++){const deps = effectFn.deps[i];// 移除deps.delete(effectFn);}// 最后重置effectFn.deps数组effectFn.deps.length = 0;}effect(() => {console.log('run');document.body.innerText = obj.text;});setTimeout(() => {obj.text = "hello vue3"; // 修改已定义的属性以触发依赖}, 1000);
</script>
由图所示:那么外层的副作用函数就会压到栈底,内层就在栈顶
[effect2,effect1]=> [effect1]=>[]:修改foo输出
01 'effectFn1 执行'
02 'effectFn2 执行'
03 'effectFn2 执行'
04 'effectFn1 执行'
与键相关的副作用函数
}
// 清除函数
function clearUp (effectFn){
// 遍历然后进行删除
for(let i = 0 ; i < effectFn.deps.length ; i++){
const deps = effectFn.deps[i];
// 移除
deps.delete(effectFn);
}
// 最后重置effectFn.deps数组
effectFn.deps.length = 0;
}
effect(() => {
console.log(‘run’);
document.body.innerText = obj.text;
});
setTimeout(() => {
obj.text = “hello vue3”; // 修改已定义的属性以触发依赖
}, 1000);
[外链图片转存中...(img-84C5fgLt-1747235972579)]由图所示:那么外层的副作用函数就会压到栈底,内层就在栈顶[effect2,effect1]=> [effect1]=>[]:修改foo输出
01 ‘effectFn1 执行’
02 ‘effectFn2 执行’
03 ‘effectFn2 执行’
04 ‘effectFn1 执行’
相关文章:

Vue.js---嵌套的effect与effect栈
4.3嵌套的effect与effect栈 1、嵌套的effect effect是可以发生嵌套的 01 effect(function effectFn1() { 02 effect(function effectFn2() { /* ... */ }) 03 /* ... */ 04 })有这么一段代码: 01 // 原始数据 02 const data { foo: true, bar: true } 03 /…...

AAAI-2025 | 电子科大类比推理助力精准识别!SPAR:基于自提示类比推理的无人机目标探测技术
作者: Nianxin Li, Mao Ye, Lihua Zhou, Song Tang, Yan Gan, Zizhuo Liang, Xiatian Zhu 单位:电子科技大学计算机科学与工程学院,上海理工大学机器智能研究所,重庆大学计算机学院,谢菲尔德大学,萨里大学…...
考研复习全年规划
25考研以330分成功上岸。 备考期间,我深知学习规划的重要性,为大家精心整理了一份初试备考时间线任务规划,希望能为正在备考的同学们提供参考。如果你对如何规划学习路线仍感迷茫,不妨参考这份时间表,合理分配时间&…...
信息安全入门基础知识
信息安全是保护信息系统和数据免受未经授权的访问、使用、披露、中断、修改或破坏的实践。对于个人和组织来说,了解信息安全的基础知识至关重要。 1. CIA三元组 信息安全的三个主要目标,也称为CIA三元组: 机密性(Confidentiality): 确保信息不被未经授权的人访问或披露完整性…...

速查 Linux 常用指令 II
目录 一、网络管理命令1. 查看和配置网络设备:ifconfig1)重启网络命令2)重启网卡命令 2. 查看与设置路由:route3. 追踪网络路由:traceroute4. 查看端口信息和使用情况1)netstat 命令2)lsof 命令…...

IIS服务器URL重写配置完整教程
1.下载URL Rewrite Module 2.1 https://www.iis.net/downloads/microsoft/url-rewrite https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_zh-CN.msi 2.安装...

注解和 XML 两种方式有什么区别?
注解和 XML 是两种常见的配置方式(尤其在 Java 开发中,如 Spring 框架),它们的主要区别体现在配置方式、代码耦合性、可读性、维护性等方面。以下是两者的对比: 1. 配置方式 注解(Annotation) 在…...

高速系统设计实例设计分析二
6.6 仿真约束的生成和实施 进行到这一步,我们已经完成了对实例进行仿真的所有条件的设置,包括对板子的设计要求分析和预布局处理。虽然从技术上讲,我们可以开始进行仿真分析并生成设计的约束,但是根据作者的工作经验,…...
计算机过程控制干燥操作实训装置JG-SX210化工单元操作实训装置
计算机过程控制干燥操作实训装置JG-SX210化工单元操作实训装置 一、装置功能 主要工艺设备采用卧式多室流化床干燥器,旋风分离器及布袋过滤器等。可以测定床层温度、差压曲线;恒定状态下介质湿度曲线和干燥速率曲线的测定。可以进行干燥物流的预热温控&…...

【MySQL】变更缓冲区:作用、主要配置以及如何查看
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...

C2S-Scale:Cell2Sentence v2
目前的单细胞基础模型(scFMs)在可扩展性、跨多种任务的灵活性以及整合文本信息的能力方面仍然有限。基于Cell2Sentence(C2S)框架展开工作,该框架将单细胞RNA测序(scRNA-seq)图谱表示为文本形式的…...
vim启动的时候,执行gg
在 Vim 编辑器中,gg 命令是一个非常有用的命令,它可以将光标快速移动到当前窗口的顶部(即第一行)。如果你想在 Vim 启动时自动执行 gg 命令,有几种方法可以实现这一点: 方法 1:使用 Vim 的启动…...
SQLServer如何为数据库创建只读账号,并测试是否只读成功。直接代入替换就行,全流程,新手替换复制即可。
以下是为数据库 SQL_Xxxx 创建一个名为 zhidu_os 的只读账号,并将其密码设置为 SQL_Passwd 的详细步骤: 步骤 1:创建登录名 在 SQL Server 实例级别创建一个登录名(Login),并设置密码。 USE master; GO-…...
Linux 内核 IPv4 协议栈中的协议注册机制解析
1. 引言 在 Linux 内核的 IPv4 协议栈中,inetsw 是一个核心数据结构,负责管理不同套接字类型(如 SOCK_STREAM、SOCK_DGRAM)的协议实现。本文结合代码分析,深入探讨其设计原理、动态协议注册机制及并发安全实现。 2. inetsw 的结构与作用 2.1 定义与初始化 static struc…...
Room持久化库:从零到一的全面解析与实战
简介 在Android开发中,Room作为官方推荐的数据库持久化库,提供了对SQLite的抽象层,使得数据库操作更加安全、高效且易于维护。 Room通过注解处理器和编译时验证,显著降低了数据库操作的复杂度,同时支持响应式编程模式,使开发者能够轻松实现数据变化的实时监听。对于企业…...

技术伦理双轨认证如何重构AI工程师能力评估体系——基于AAIA框架的技术解析与行业实证研究
引言:AI工程师能力评估的范式转型 2025年全球人工智能产业呈现出两大特征:技术迭代加速与监管框架完善。据Gartner数据显示,全球75%的企业在AI项目部署中遭遇技术伦理混合型难题,传统单维度技术认证体系已无法满足产业需求。本文…...
VSCode python配置
配置解释器 按 CtrlShiftP 打开命令面板。 输入 Python: Select Interpreter 并回车。 参数调试 在launch.json中添加 {"version": "0.2.0","configurations": [{"name": "Python: Current File","type": &quo…...

ubuntu20.04系统搭建k8s1.28集群-docker作为容器运行时
ubuntu系统搭建 ubuntu-22.04.5-desktop-amd64.iso映像文件--->实际却是20.4focal版本。 【安装过程没有特别指出的默认回车下一步】 【用户和密码设置】 【网络连接】 【在vmware上安装的话,网络配置如下】【在vm里配置选择nat或者桥接即可】 【国内源配置】&…...

【Alist+RaiDrive挂载网盘到本地磁盘】
1.安装准备 安装RaiDrive RaiDrive - 像 USB 驱动器一样安装云存储 安装alist 安装方式请查看官网: AList文档 2.启动Alist(docker) docker官网 Install | Docker EngineDocker Desktop | Docker Docs 运行容器 docker run -d --restartalways -v /home/alist:/opt/alist/…...
vue 去掉右边table的下拉条与下面的白色边框并补充满
::v-deep table {width: 100% !important; } ::v-deep .el-table::after, .el-table::before {display: none !important; }/* 隐藏滚动条但保留滚动功能 */ ::v-deep .el-table__body-wrapper::-webkit-scrollbar {width: 0 !important;height: 0 !important; }::v-deep .el-t…...

vue实现进度条带指针
效果最终 function calculatePointerPosition(value) {if (value < 2.6) return 12.5; // 非常差位置if (value < 5.1) return 37.5; // 较差位置if (value < 7.1) return 62.5; // 良好位置return 90; // 非常满意位置 }function getStatusText(value) {if (valu…...

Kafka Go客户端--Sarama
Kafka Go客户端 在Go中里面有三个比较有名气的Go客户端。 Sarama:用户数量最多,早期这个项目是在Shopify下面,现在挪到了IBM下。segmentio/kafka-go:没啥大的缺点。confluent-kafka-go:需要启用cgo,跨平台问题比较多,交叉编译也…...
蓝桥杯12届国B 纯质数
题目描述 如果一个正整数只有 1 和它本身两个约数,则称为一个质数(又称素数)。 前几个质数是:2,3,5,7,11,13,17,19,23,29,31,37,⋅⋅⋅ 。 如果一个质数的所有十进制数位都是质数,我们称它为纯质数。例如࿱…...
从规则驱动到深度学习:自然语言生成的进化之路
自然语言生成技术正经历着人类文明史上最剧烈的认知革命。这项起源于图灵测试的技术,已经从简单的符号操作演变为具备语义理解能力的智能系统。当我们回溯其发展历程,看到的不仅是算法模型的迭代更新,更是一部人类认知自我突破的史诗。这场革…...
【C/C++】深度探索c++对象模型_笔记
1. 对象内存布局 (1) 普通类(无虚函数) 成员变量排列:按声明顺序存储,但编译器会根据内存对齐规则插入填充字节(padding)。class Simple {char a; // 1字节(偏移0)int b; …...

RustDesk:开源电脑远程控制软件
RustDesk:开源电脑远程控制软件 RustDesk:开源电脑远程控制软件一、RustDesk 简介二、下载教程2.1 桌面版下载2.2 Android 版下载 三、安装教程3.1 桌面版安装 四、功能讲解4.1 远程控制4.2 文件传输4.3 安全可靠4.4 自定义服务器 五、RustDesk技术架构解…...

[操作系统] 策略模式进行日志模块设计
文章目录 [toc]一、什么是设计模式?二、日志系统的基本构成三、策略模式在日志系统中的落地实现✦ 1. 策略基类 LogStrategy✦ 2. 具体策略类▸ 控制台输出:ConsoleLogStrategy▸ 文件输出:FileLogStrategy 四、日志等级枚举与转换函数五、日…...
掌握Docker Commit:轻松创建自定义镜像
使用 docker commit 命令可以通过对现有容器进行修改来创建新的镜像。-a 选项用于指定作者信息,-m 选项用于添加提交信息。以下是具体步骤: 启动并修改容器 启动一个容器并进行必要的修改。例如,启动一个 Ubuntu 容器并安装一些软件包&…...

MoonBit正式入驻GitCode!AI时代的编程语言新星,开启高性能开发新纪元
在AI与编程语言深度交融的今天,开发者们正见证一场技术生产力的革命。由IDEA研究院基础软件中心倾力打造的MoonBit(月兔)编程语言,自2023年横空出世以来,凭借高性能、低延迟、轻量化的特性,迅速成为全球开发…...
命令行快速上传文件到SFTP服务器(附参考示例)
一、SFTP基础命令格式 更新参数后的标准命令格式为: sftp -P [端口号] [用户名][服务器IP]:[远程路径] <<< $put [本地文件路径]二、新参数实例解析 使用新连接参数的完整命令示例: sftp -P 30033 test_jigou_sftp121.199.64.216:/download…...