Vue3响应式系统(三)

Vue3响应式系统(二)icon-default.png?t=N7T8https://blog.csdn.net/qq_55806761/article/details/135612738

七、无限递归循环。

        响应式系统里无限递归也是需要考虑到的。

     什么情况会出现无限递归循环?

        代码示例: 

const data = { foo: 1 }
const obj = new Proxy(/* * */)
effect(() => {
  obj.foo++
}) 

        obj.foo++会直接导致栈溢出,如图所示:

        为何会这样呢?

        obj.foo++  ====>   obj.foo = obj.foo + 1 ,首先会读取obj.foo的值,这就会触发get函数中的track收集函数,之后又会将obj.foo的值赋值给obj.foo,这时又会触发trigger触发函数。读取触发的副作用函数还没有执行完,又要去设置执行副作用函数,无限循环的去调用自己,所以产生了栈溢出。 所以,既会读取值,又会设置值,这就是出现栈溢出的根本原因。

       如何解决呢?

        由于读取和设置都是在同一个副作用函数中执行的,无论是track收集到的还是trigger触发的都是activeEffect,不变。所以,我们可以在trigger增加一个守卫:如果trigger触发的执行的副作用函数与当前正在执行的副作用函数一样,便不触发执行。

        更改trigger触发函数:

function trigger(target, key) {
  const depsMap = bucketMap.get(target)
  if (!depsMap) return
  const effects = depsMap.get(key)
  const effectToRun = new Set()



  //增加守卫
  effects && effects.forEach(effectFn => {
    if(effectFn != activeEffect) {
      effectToRun.add(effectFn)
    }
  })
  effectToRun && effectToRun.forEach(effectFn => effectFn())
}

        此时,便不会无限循环,而是只执行一次。 

八、调度执行。

        可调度性是响应式系统非常重要的的特性。

      可调度性?

        简单来说就是,trigger触发副作用函数执行的时候,可以去决定副作用函数执行的时机、次数以及方式。

        示例:(结果如图所示)

// 依据上文扩展案例

effect(() => {
  console.log(obj.foo);
})

obj.foo++

console.log('结束了')

     如果我要实现下面的效果呢?(不改变代码顺序)

        这时就需要响应式系统支持可调度我们可以为effect函数设置一个选项参数options,允许用户执行调度器。 

effect(
  () => {
    console.log(obj.foo);
  },
  // options
  {
    // 调度器 scheduler 是一个函数
    scheduler(fn) {
      // ...
    }
  }
)

        更改effect函数 

function effect(fn, options = {}) {
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn
    effectStack.push(effectFn)
    fn()
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
  }



  // 将 options 挂载到 effectFn 上
  effectFn.options = options



  effectFn.deps = []
  effectFn()
}

        根据options,就要来更改trigger函数了

function trigger(target, key) {
  const depsMap = bucketMap.get(target)
  if (!depsMap) return
  const effects = depsMap.get(key)
  const effectToRun = new Set()
  effects && effects.forEach(effectFn => {
    if(effectFn != activeEffect) {
      effectToRun.add(effectFn)
    }
  })


  effectToRun && effectToRun.forEach(effectFn => {
    // 如果存在调度器,则调用这个调度器,并将副作用函数作为参数传递
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else { // 没有调度器则直接执行副作用函数
      effectFn()
    }
  })


}

        去测试:将副作用函数加入宏任务队列中执行

effect(
  () => {
    console.log(obj.foo);
  },
  // options
  {
    // 调度器 scheduler 是一个函数
    scheduler(fn) {
      // 将副作用函数加入宏任务队列
      setTimeout(fn)
    }
  }
)
console.log('结束了')
obj.foo++

        如图所示,效果得以实现。

      除了调度执行顺序,还可以做到调度执行次数。

effect(
  () => {
    console.log(obj.foo);
  }
)
obj.foo++
obj.foo++

/**
 * 打印结果为:
 * 1
 * 2
 * 3
*/

        从1到3,2只是过渡,我们并不关心,所以执行三次有点多余。我们希望只打印:(不包含过度)

/**
 * 打印结果为:
 * 1
 * 3
*/

         基于调度器我们实现一下子: 

//定义一个任务队列
const jobQueue = new Set()
// 使用 Promise.resolve() 创建一个 promise 实例,我们用它将一个任务添加到微任务队列中
const p = Promise.resolve()

//一个标志代表是否正在刷新队列
let isFlushing = false
function flushJob() {
  // 如果队列正在刷新, 则什么都不做
  if(isFlushing) return 
  // 正在刷新设置
  isFlushing = true
  // 在微任务对类中刷新JobQueue队列
  p.then(() => {
    jobQueue.forEach(job => job())
  }).finally(() => {
    //结束后重置 isFlushing
    isFlushing = false
  })
}

// 测试
effect(
  () => {
    console.log(obj.foo);
  },
  {
    scheduler(fn) {
      jobQueue.add(fn)
      flushJob()
    }
  }
)
obj.foo++
obj.foo++
/**
 * 打印:
 * 1
 * 3
*/

        当obj.foo执行两次自增操作,会同步且连续执行两次scheduler调度函数,这意味着同一个副作用函数会被jobQueue.add(fn)语句添加两次,由于Set去重能力,最终jobQueue中只会有一项,即当前副作用函数。同理,flushJob也会同步执行两次,由于isFlushing标志的存在,实际上flushJob函数在一个事件循环内只会执行一次,即在微任务队列内执行一次。当微任务队列开始执行的时候,jobQueue队列就会遍历里面存的副作用函数,当副作用函数调用的时候obj.foo已经是3了,这样我们就实现了所期望的值。

        其实这个功能类似于Vue.js中连续多次修改响应式数据但只会触发一次更新,实际上Vue.js内部实现了一个更加完善的调度器,思路与上面代码思路相同。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

金融CRM系统是什么?有哪些功能和作用

今年市场经济下行,投资趋向于保守、人们消费降级,对于金融行业来说影响很大。受经济形式的影响加上行业的数字化转型升级,金融企业都在寻求客户管理的新策略,维护好忠实客户、吸引新客户投资。小编认为CRM系统是管理客户的不二之选…

LLM之RAG实战(十六)| 使用Llama-2、PgVector和LlamaIndex构建LLM Rag Pipeline

近年来,大型语言模型(LLM)取得了显著的进步,然而大模型缺点之一是幻觉问题,即“一本正经的胡说八道”。其中RAG(Retrieval Augmented Generation,检索增强生成)是解决幻觉比较有效的…

ROS第 6 课 编写简单的订阅器 Subscriber

文章目录 第 6 课 编写简单的订阅器 Subscriber1. 编写订阅者节点2. 测试发布者和订阅者 第 6 课 编写简单的订阅器 Subscriber 订阅器是基于编辑了发布器的基础上创建的,只有发布了消息,才有可能订阅。若未编辑发布器,可前往"ROS第5课 …

C语言练习day7

数包含9的数 包含数字9的数_牛客题霸_牛客网 题目: 思路:首先,我们得生成1~2019之间的数字,其次再通过各个位数来比较是否等于9,若等于,则记录一次,反之,不记录。 代码演示&…

MATLAB - 加载预定义的机器人模型

系列文章目录 前言 一、 要快速访问常见的机器人模型,可使用 loadrobot 功能,该功能可加载市售的机器人模型,如 Universal Robots™ UR10 cobot、Boston Dynamics™ Atlas 人形机器人和 KINOVA™ Gen 3 机械手。探索如何生成关节配置并与机器…

SAP银企直联报错排查方法与步骤-F110

银企直联的报错排查经常需要利用F110来查询。方法步骤如下: 1、首先要确定报错是哪天的,并且当天那一次跑的付款建议。需要通过表 REGUH来确认(跟据供应商编码、日期) 2、通过REGUH表的信息知道了是2024年1月16号第5个标识(也就是第五次跑付…

【QML COOK】- 010-动态创建组件

上节介绍了Component的概念,本节介绍一下如何使用javascript动态创建对象。 1. 创建工程,新建一个MyComponent.qml的qml import QtQuickRectangle {color: "red" }它很简单就是一个红色框 2. 编辑main.qml import QtQuickWindow {id: root…

刘知远LLM入门到实战——自然语言基础

文章目录 自然语言处理基础词表示语言模型N-gram ModelNeural Language Model: 为什么NLP等领域的模型越来越大? 大模型会带来哪些新的范式和挑战? 自然语言处理基础 让计算机理解人类语言,图灵测试就是基于对话的方式。 研究历史&#xff…

SpringBoot:详解依赖注入和使用配置文件

🏡浩泽学编程:个人主页 🔥 推荐专栏:《深入浅出SpringBoot》《java项目分享》 《RabbitMQ》《Spring》《SpringMVC》 🛸学无止境,不骄不躁,知行合一 文章目录 前言一、&#x1f3…

django后台手机号加密存储

需求: 1 :员工在填写用户的手机号时,直接填写,在django后台中输入 2:当员工在后台确认要存储到数据库时,后台将会把手机号进行加密存储,当数据库被黑之后,手机号字段为加密字符 3&am…

青少年的敏感心理

这几天的某个闲暇时刻,突然想起一个有意思的话题。关于青少年的心理,尤其是青春期的心理,这是敏感的一种心理状态。 依稀记得那大概是初一或者是更低年级的样子,当时父母外出务工,高中以前都是和奶奶一起长大。具体事…

数据科学与大数据导论期末复习笔记(大数据)

来自于深圳技术大学,此笔记涵盖了期末老师画的重点知识,分享给大家。 等深分箱和等宽分箱的区别:等宽分箱基于数据的范围来划分箱子,每个箱子的宽度相等。等深分箱基于数据的观测值数量来划分箱子,每个箱子包含相同数量…

跟着cherno手搓游戏引擎【8】按键和鼠标的KeyCode

自定义KeyCode 先把glfw3.h里的KeyCode的定义抄到咱这里来。 在YOTO下创建KeyCode.h: #pragma once#ifdef YT_PLATFORM_WINDOWS///从glfw3中拿的 #define YT_KEY_SPACE 32 #define YT_KEY_APOSTROPHE 39 /* */ #define YT_KEY_COMMA 44…

Video 不支持微信小程序的show-bottom-progress属性

原文地址:Video 不支持微信小程序的show-bottom-progress属性-鹭娃网络 相关平台 微信小程序 小程序基础库: 2.20.1使用框架: React 复现步骤 import { Video} from tarojs/components; 渲染一个Video播放视频,无法隐藏手机屏幕最底部的进度条&#…

springcloud Alibaba中gateway和sentinel联合使用

看到这个文章相信你有一定的sentinel和gateway基础了吧。 官网的gateway和sentinel联合使用有些过时了,于是有了这个哈哈,给你看看官网的: 才sentinel1.6,现在都几了啊,所以有些过时。 下面开始讲解: 首先…

day02_计算机常识丶第一个程序丶注释丶关键字丶标识符

计算机常识 计算机如何存储数据 计算机世界中只有二进制。那么在计算机中存储和运算的所有数据都要转为二进制。包括数字、字符、图片、声音、视频等。 进制 进制也就是进位计数制,是人为定义的带进位的计数方法 实例: // 在java 中 可以使用不同…

Linux实操学习

Linux常用操作 一、帮助命令1. man1.1 基本语法1.2 快捷键1.3 注意事项 2. help2.1 基本语法2.2 注意事项 3. 常用快捷键 二、文件目录类1. 常规操作1.1 pwd1.2 cd1.3 ls 2. 文件夹操作2.1 mkdir2.2 rmdir 3. 文件操作3.1 touch3.2 cp3.3 rm3.4 mv 4. 文件查看4.1 cat4.2 more4…

【视觉SLAM十四讲学习笔记】第五讲——相机模型

专栏系列文章如下: 【视觉SLAM十四讲学习笔记】第一讲——SLAM介绍 【视觉SLAM十四讲学习笔记】第二讲——初识SLAM 【视觉SLAM十四讲学习笔记】第三讲——旋转矩阵 【视觉SLAM十四讲学习笔记】第三讲——旋转向量和欧拉角 【视觉SLAM十四讲学习笔记】第三讲——四元…

部署本地GPT

在现实生活中,很多公司或个人的资料是不愿意公布在互联网上的,但是我们又要使用人工智能的能力帮我们处理文件、做决策、执行命令那怎么办呢?于是我们构建自己或公司的本地专属GPT变得非常重要。 先看效果: 查资料不用愁 家教不…

CF1178F2 Long Colorful Strip 题解 搜索

Long Colorful Strip 传送门 题面翻译 题目描述 这是 F 题的第二个子任务。F1 和 F2 的区别仅在对于 m m m 和时间的限制上 有 n 1 n1 n1 种颜色标号从 0 0 0 到 n n n,我们有一条全部染成颜色 0 0 0 的长为 m m m 的纸带。 Alice 拿着刷子通过以下的过…
最新文章