当前位置: 首页 > news >正文

Vue.js---避免无限递归循环 调度执行

4.4 避免无限递归循环

什么情况下会无限递归?

01 const data = { foo: 1 }
02 const obj = new Proxy(data, { /*...*/ })
03
04 effect(() => obj.foo++)

例如这种情况,它会反复设置添加一直到栈溢出

首先读取obj.foo 的值,这会触发 track 操作,将当前副作用函数收集到“桶”中,接着将其加 1 后再赋值给 obj.foo,此时会触发 trigger 操作,即把“桶”中的副作用函数取出并执行。但问题是该副作用函数正在执行中,还没有执行完毕,就要开始下一次的执行。这样会导致无限递归地调用自己,于是就产生了栈溢出。

如何解决?

因为读取和设置操作是在同一个副作用函数内进行的。如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行。

        // 开始执行const effectsToRun = new Set(effects);effects && effects.forEach(effectFn => {if(activeEffect !== effectFn){effectsToRun.add(effectFn);}});

4.5 调度执行

什么是可调度性:当trigger动作触发副作用函数重新执行时,有能力决定副作用函数执行的时机、次数以及方式。

01 const data = { foo: 1 }
02 const obj = new Proxy(data, { /* ... */ })
03
04 effect(() => {
05   console.log(obj.foo)
06 })
07
08 obj.foo++
09
10 console.log('结束了')

这一段代码输出:

1
2
'结束了'

假如现在需求有变,我们想要它输出:

1
'结束了'
2

这时候我们想要一种可以不改变代码的位置,就可以实现这种效果的方式---->调度执行

我们可以为 effect 函数设计一个选项参数options,允许用户指定调度器:

  function effect (fn , options = {}) {const effectFn = () => {// 调用clearup函数完成清除工作clearUp(effectFn);activeEffect = effectFn; // 在调用副作用函数之前将副作用函数压入栈中effectStack.push(effectFn);fn();// 执行完之后抛出,把activeEffect还原成原来的值effectStack.pop();activeEffect = effectStack[effectStack.length - 1];}// 将options挂在到fn上effectFn.options = options;// deps用来存储所有与该副作用函数相关联的依赖集合effectFn.deps = [];// 执行副作用函数effectFn();}

trigger函数当中,在副作用函数执行之前,根据调度内容进行调度

// 触发变化:处理调度逻辑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 =>{if(effectFn.options.scheduler){effectFn.options.scheduler(effectFn);}else {effectFn();}});// effects && effects.forEach(fn => fn()); // 只触发与键相关的副作用函数}

连续多次修改只进行最后一次的更新:

    // 连续执行同一代码,只饭后最后一次计算的结果// 定义一个任务集合set:可以进行去重操作const jobQueue = new Set();// 使用promise.resolve()创建一个promise实例:将一个任务添加到微任务队列当中const p = Promise.resolve();// 开始刷新队列let isFlushing = false;function flushJob(){// 如果队列正在被刷新->returnif(isFlushing){return;}// 没有刷新,进行刷新操作isFlushing = true;p.then(() => {jobQueue.forEach(job => job());}).finally(() => {// 结束后重置isFlushing = false;})}effect(() => {console.log(obj.foo);} , {scheduler(fn){jobQueue.add(fn);//调用flushJob刷新队列flushJob();}})obj.foo++;obj.foo++;

目前的响应代码:

<script setup>let activeEffect;// effect栈const effectStack = [];function effect (fn , options = {}) {const effectFn = () => {// 调用clearup函数完成清除工作clearUp(effectFn);activeEffect = effectFn; // 在调用副作用函数之前将副作用函数压入栈中effectStack.push(effectFn);fn();// 执行完之后抛出,把activeEffect还原成原来的值effectStack.pop();activeEffect = effectStack[effectStack.length - 1];}// 将options挂在到fn上effectFn.options = options;// deps用来存储所有与该副作用函数相关联的依赖集合effectFn.deps = [];// 执行副作用函数effectFn();}const bucket = new WeakMap();const data = { foo : 1 }; // 确保所有属性都已定义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);effects && effects.forEach(effectFn => {if(activeEffect !== effectFn){effectsToRun.add(effectFn);}});effectsToRun.forEach(effectFn =>{if(effectFn.options.scheduler){effectFn.options.scheduler(effectFn);}else {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(obj.foo);//   })// obj.foo ++;//   obj.foo++;// 连续执行同一代码,只饭后最后一次计算的结果// 定义一个任务集合set:可以进行去重操作const jobQueue = new Set();// 使用promise.resolve()创建一个promise实例:将一个任务添加到微任务队列当中const p = Promise.resolve();// 开始刷新队列let isFlushing = false;function flushJob(){// 如果队列正在被刷新->returnif(isFlushing){return;}// 没有刷新,进行刷新操作isFlushing = true;p.then(() => {jobQueue.forEach(job => job());}).finally(() => {// 结束后重置isFlushing = false;})}effect(() => {console.log(obj.foo);} , {scheduler(fn){jobQueue.add(fn);//调用flushJob刷新队列flushJob();}})obj.foo++;obj.foo++;</script>

effectFn);
}
// 最后重置effectFn.deps数组
effectFn.deps.length = 0;
}

// effect(() => {
// console.log(obj.foo);
// })
// obj.foo ++;
// obj.foo++;
// 连续执行同一代码,只饭后最后一次计算的结果
// 定义一个任务集合set:可以进行去重操作
const jobQueue = new Set();
// 使用promise.resolve()创建一个promise实例:将一个任务添加到微任务队列当中
const p = Promise.resolve();
// 开始刷新队列
let isFlushing = false;
function flushJob(){
// 如果队列正在被刷新->return
if(isFlushing){
return;
}
// 没有刷新,进行刷新操作
isFlushing = true;
p.then(() => {
jobQueue.forEach(job => job());

    }).finally(() => {// 结束后重置isFlushing = false;})
}effect(() => {console.log(obj.foo);
} , {scheduler(fn){jobQueue.add(fn);//调用flushJob刷新队列flushJob();}
})
obj.foo++;
obj.foo++;
```
http://www.xdnf.cn/news/473239.html

相关文章:

  • Weblogic SSRF漏洞复现(CVE-2014-4210)【vulhub靶场】
  • 黑马Java基础笔记-11
  • 深度学习之用CelebA_Spoof数据集搭建一个活体检测-训练好的模型用MNN来推理
  • Turbo C++
  • 数据驱动下的具身智能进化范式
  • 专项智能练习(定义判断)
  • 学习笔记:黑马程序员JavaWeb开发教程(2025.4.4)
  • threejs 大场景优化方案(代码层)
  • pycharm中qthread中的run函数debug不上的问题
  • 深度学习中的提示词优化:梯度下降全解析
  • 钉钉数据与金蝶云星空的无缝集成解决方案
  • mavgenerate 在 win11 下环境搭建注意问题
  • Kuberbetes-CA证书过期解决方案
  • linux系统中如何校准时间
  • windows、Ubuntu、Debian 添加静态路由
  • 从零开始学习PX4源码22(位置控制器---加速度部分理解)
  • MyBatis XML配置和入门使用
  • 论在中断中的标志变量使用volatile的重要性分析
  • 基于EtherCAT与ABP vNext 构建高可用、高性能的工业自动化平台
  • 双重差分模型学习笔记4(理论)
  • ip命令详解
  • HPC软件使用之ANSYS Fluent
  • Git-学习笔记(粗略版)
  • SpringBoot中的Lombok库
  • Python模块化编程
  • 俄罗斯方块算法
  • 2025年长三角+山东省赛+ 认证杯二阶段资料助攻说明
  • 简单网络交换、路由-华三MVRP
  • Linux动态库热加载:动态库与主程序符号调用机制总结
  • 6、登录功能后端开发