当前位置: 首页 > 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++;
```

相关文章:

  • Elasticsearch 常用语法手册
  • [吾爱出品] 中医问诊辅助记录软件
  • ES常识8:ES8.X如何实现热词统计
  • CPU cache基本原理
  • 基于javaweb的JSP+Servlet家政服务系统设计与实现(源码+文档+部署讲解)
  • 2900. 最长相邻不相等子序列 I
  • Windows注册表备份与恢复指南
  • AI大模型:(二)2.5 人类对齐训练自己的模型
  • JDK 1.8 全解析:从核心特性到企业实战的深度实践
  • WEB安全--Java安全--LazyMap_CC1利用链
  • 技术更新频繁,团队如何适应变化
  • 使用Python开发经典俄罗斯方块游戏
  • 海外短剧H5/App开源系统搭建指南:多语言+国际支付+极速部署
  • 9. 表的内连和外连
  • 如何在Google Chrome浏览器里-安装梦精灵AI提示词管理工具
  • 记录一下seata后端数据库由mariadb10切换到mysql8遇到的SQLException问题
  • 让三个线程(t1、t2、t3)按顺序依次打印 A、B、C
  • 入门消息队列
  • C# Try Catch Finally 执行顺序是什么?有返回值呢?
  • Google DeepMind 推出AlphaEvolve
  • 国家统计局向多省份反馈统计督察意见
  • 端午小长假前夜火车票今日开抢,多个技巧提高购票成功率
  • 国税总局上海市税务局回应刘晓庆被举报涉嫌偷漏税:正依法依规办理
  • 共建医学人工智能高地,上海卫健委与徐汇区将在这些方面合作
  • 上海“城市文明开放麦”全城总动员,樊振东担任首位上海城市文明大使
  • 株洲一重病妇女被要求本人到银行取款时去世?当地警方:正在处理