深入理解Vue3.js响应式系统设计之调度执行

news2024/11/26 20:35:59

如果您觉得这篇文章有帮助的话!给个点赞和评论支持下吧,感谢~

作者:前端小王hs

阿里云社区博客专家/清华大学出版社签约作者/csdn百万访问前端博主/B站千粉前端up主

此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来

书籍:《Vue.js设计与实现》 作者:霍春阳

本篇博文将在书第1.7节的基础上进一步解析,附加了测试的代码运行示例,以及对书籍中提到的ES6中的数据结构及其特点进行阐述,方便正在学习Vue3想分析Vue3源码的朋友快速阅读

如有帮助,不胜荣幸

前置章节:

  1. 深入理解Vue3.js响应式系统基础逻辑
  2. 深入理解Vue3.js响应式系统设计之栈结构和循环问题

调度执行

在前面的章节我们学习到,只要修改了代理对象obj的属性就会触发trigger()执行,那么在4.7节,作者向我们介绍了如何去实现可控制的去执行trigger(),也就是决定副作用函数执行的时机、次数以及方式(原文)

那么在这一节,将会涉及到宏任务微任务知识,这是JavaScript高阶知识,我们先来介绍下

JavaScript中,异步任务被分为两种主要的类别:宏任务(MacroTask)和微任务(MicroTask)

这两种任务类型在事件循环(Event Loop) 中被处理,但它们有不同的执行优先级和时机

宏任务包括:

  1. script(整体代码)
  2. setTimeout
  3. setInterval
  4. setImmediate(Node.js环境)
  5. I/O
  6. UI渲染(浏览器)
  7. MessageChannel(消息通道)
  8. postMessage
  9. requestAnimationFrame(浏览器)

微任务包括:

  1. Promise.then() 或 .catch()
  2. MutationObserver(浏览器)
  3. process.nextTick(Node.js环境)
  4. queueMicrotask()(较新的API)

执行的过程是,在每次宏任务执行完后,会检查微任务队列是否有微任务,如果有,那么执行(且执行完所有微任务),如果没有,则从宏任务队列中继续执行下一个宏任务。也就是说,微任务是在两个宏任务之间执行的

而如果存在同步代码,则宏任务队列异步函数会在同步代码执行后再执行,可看下面这个例子:

执行情况

现在,我们来看一个需求

const data = { foo: 1 };
const obj = new Proxy(data, { /* ... */ });

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

obj.foo++;

console.log('结束了');

这段代码中,正常的输出顺序是12、最后是结束了,这点非常简单,但如果想把2放在结束了之后呢?那么很明显,就需要控制副作用函数的执行时机

结合上面我们说的宏任务,是不是思路就来了?把结束了这个script语句放到执行完fn()之后即可,下面我们来看看是如何实现的

第一步:给effect函数涉及一个选项参数options,其实就是传入一个对象,对象内是一个可执行函数,参数为fn(),代码如下:

// 书中源代码
effect(
  () => {
    console.log(obj.foo);
  },
  // options
  {
    // 调度器 scheduler 是一个函数  
    scheduler(fn) {
      // ... 这里是调度器函数的实现
    }
  }
);

第二步:在effect内部将options挂载到effectFn上,代码如下:

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

第三步:在trigger中进行判断,如果effectFn.options存在scheduler,那么执行scheduler,代码如下:

function trigger(target, key) {
  const depsMap = bucket.get(target)
  if (!depsMap) return
  const effects = depsMap.get(key)

  const effectsToRun = new Set(
  effects && effects.forEach(effectFn => {
    if (effectFn !== activeEffect) {
      effectsToRun.add(effectFn)
    }
  })

  effectsToRun.forEach(effectFn => {
    // 如果一个副作用函数存在调度器,则调用该调度器,并将副作用函数作为参数传递
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else {
      // 否则直接执行副作用函数(之前的默认行为)
      effectFn()
    }
  })
}

那么现在,当我们传入scheduler(fn)之后,就会被保存在effectFn.options中,当再次触发trigger时,就会执行effectFn.optionsfn(),关键是scheduler里应该怎么处理呢?其实非常简单,只需添加一个setTimeout,然后在定时器里执行fn(),还记得我们在上面说过,这是一个宏任务,代码如下:

// effect()内
{
  scheduler(fn) {  
    // 将副作用函数放到宏任务队列中执行  
    setTimeout(fn);
  }
}

现在,我们来分析一下代码的执行顺序:

  1. 执行effect,那么会输出1,以及把scheduler(fn){}传给options
  2. 执行obj.foo++,那么先会涉及到一个读取,但这里我们无需关心读取,只看trigger
  3. trigger中执行effectFn.optionsfn(),那么这是一个宏任务,会被送进队列中等待当前宏任务执行完成
  4. 由于setTimeout是一个异步函数,而console.log('结束了')是一个同步代码,所以会先输出结束了,然后再输出完成了++之后的2

下面,我们再来看一个更加进阶的操作,我们直接分析需求和实现过程。首先是需求,代码如下:

const data = { foo: 1 };
const obj = new Proxy(data, { /* ... */ });  

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

obj.foo++;  
obj.foo++;

通过逻辑,我们知道会按顺序输出123,现在的需求是直接输出13,也就是跳过21我们知道,直接由fn()初次执行就会输出,那3呢?

我们来看一下解决的实现过程,代码如下:

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

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

effect(() => {
    console.log(obj.foo);
}, {
    scheduler(fn) {
        // 每次调度时,将副作用函数添加到 jobQueue 队列中
        jobQueue.add(fn);
        // 调用 flushJob 刷新队列
        flushJob();
    }
});

obj.foo++;
obj.foo++;

我们分析一下实现的过程:

  1. 执行effect,那么首先输出当前的1,并且把scheduler里的函数放进effectFn.options
  2. 执行obj.foo++,读取我们还是不分析,我们直接看触发时执行scheduler里的函数
  3. 执行jobQueue.add(fn),这是一个Set数据结构,也就是里面的值是唯一的
  4. 执行flushJob,将isFlushing置为true,然后把jobQueue里的每一项fn()添加到微任务队列中,并且在执行完后会将isFlushing置为false,不过此时并没有执行
  5. 我们知道,微任务之后在宏任务执行完后才会执行,那么此时flushJob执行完了,obj.foo++也执行完成了,foo变为2
  6. 注意!flushJob同步代码obj.foo++也是同步代码,所以先执行obj.foo++,那么在这里的时候,jobQueue.add(fn);是无效的,因为Set里的都是唯一值,那么他会执行flushJob
  7. 执行flushJob时,由于前面我们已经置为了ture,所以直接return
  8. 那么当所有的同步代码执行完后,就查看微任务队列了,就把p.then里的代码拿出来执行
  9. 执行console.log(obj.foo);,此时就已经了是3了!

如何设计只执行一次?结合Set只保存唯一值、微任务的特性和函数开关(布尔值

分析的时候是不是觉得有点绕?只要搞清楚了同步和异步以及宏任务和微任务就清晰了,但步骤确实是有点多,也不得不佩服高级前端工程师的设计思路,一环套一环

如果你看到这里,那么可以和hr夸夸其谈:

  • 什么是宏任务和微任务
  • 有同步函数和异步函数、异步函数有微任务时的执行顺序
  • 如何设计只执行一次的逻辑

结语

那么到这里,我们就跟着书籍解决了如何实现调度的问题,下一节笔记我们来探讨如何实现computed,这是实现响应式核心的关键API

谢谢大家的阅读,如有错误的地方请私信笔者

笔者会在近期整理后续章节的笔记发布至博客中,希望大家能多多关注前端小王hs

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

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

相关文章

Linux 一键部署 Nginx1.26.1 + ModSecurity3

前言 ModSecurity 是 Apache 基金会的一个开源、高性能的 Web 应用程序防火墙(WAF),它提供了强大的安全规则引擎,用于检测和阻止各种攻击行为,如 SQL 注入、XSS 跨站点脚本攻击等。而 nginx 是一个高性能的 Web 服务器,常用于处理大量的并发请求,具有很高的负载均衡能力…

知网期刊《新课程导学》投稿要求及收稿方向

知网期刊《新课程导学》投稿要求及收稿方向 知网期刊《新课程导学》作为一份专注于教育领域的学术期刊,一直以来都致力于为广大学术研究者提供一个高质量、高水平的学术交流平台。为了保证期刊的学术质量,编辑部对投稿要求和收稿方向有着严格的规定。 首…

Postman接口测试详解与进阶

Postman是一个功能强大的接口测试工具,它主要用于模拟用户发起的各类HTTP请求,从而验证响应中的结果数据是否和预期值相匹配。以下是关于Postman的详细介绍: Postman是一个功能全面、使用便捷、支持多种HTTP请求类型、提供丰富的测试数据和配…

铝型材挤压车间的数字孪生应用

图扑利用数字孪生技术,在铝型材挤压车间实现了生产线的全方位实时监控和优化。通过高精度三维建模和数据可视化,提升了效率和管理透明度,促进了智能制造和资源配置的优化。

【2024亲测无坑】在Centos.7虚拟机上安装Oracle 19C

目录 一、安装环境准备 1、linux虚拟机安装 2、虚拟机快照 3、空间检查&软件上传 二、Oracle软件安装 1.preinstall安装及其他配置准备 2.oracle安装 三、数据库实例的安装 1.netca——网络配置助手 2.dbca——数据库配置助手 四、ORACLE 19C 在linux centos 7上…

c++qt合并两张灰度图像

需求:将两张尺寸相同的灰度图像进行合并,合并后的图像,每个像素点灰度值为两张原图对应像素点灰度值之和。若超过255,则最大为255。 方法一: 将图像读取为cv::Mat,再调用opencv的cv::add方法,进…

Electron快速入门(一):用VS Code快速创建html+js+css编写的项目

创建一个文件夹(例如:start或者create-electron 都是小写英文字母有的插件才不会报错),并进入该文件夹,打开 vscode创建3个文件: 1. 名为 main.js 的文件是主进程 // main.js//用于控制应用程序寿命和创建…

网络安全的双刃守护:揭秘双算法SSL证书的智慧盾牌

双算法SSL证书,这一科技与智慧的结晶,如同夜空中最亮的双子星,照亮了数据传输的幽径。它不仅继承了传统SSL证书的精髓,确保信息在传递过程中的私密与完整,更创新性地融合了两种顶尖加密算法——RSA与SM2,犹…

gateway整合sentinel限流

官方文档:https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81 从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流: route 维度:即在 Spr…

STM32的通用定时器中断编程

如果遇到需要单片机产生严格时序的场景(比如DAC输出特定模拟信号,GPIO口控制模拟开关),延时函数可能就无法胜任了。最近在工作时公司上级教会了我使用“门票”思维(中断标志位)编写单片机裸机程序,今天写一…

预制舱变电站高压室巡检机器人系统

一、背景 预制舱变电站高压室由于空间狭小、设备紧凑,传统的巡检方式往往需要人工进入高压室进行巡检,不仅存在安全风险,而且巡检效率低下,难以满足日益增长的电力设备运维需求。 二、预制舱高压室巡检机器人系统 预制舱高压室巡…

QT MQTT (二)编译与集成

一、QT MQTT 提供 MQTT 客户端服务的 Qt 专用库基于标准化发布 / 订阅协议,用于在设备和组件之间可靠地共享数据。MQTT 是为保证状态正确性、满足高安全标准和交换最小数据而设计的协议,因此被广泛应用于各种分布式系统和物联网解决方案中。 Qt开发MQT…

【SAP Abap】一条SQL语句实现支持报表项配置的财务报表

【SAP Abap】一条SQL语句实现支持报表项配置的财务报表 1、业务背景2、配置项特殊处理3、实现方式(Hana Studio SQL语句)4、实现方式(Abap OpenSQL语句)5、总结 1、业务背景 在财务三大报表之外,业务需要使用类似的科…

[创业之路-121] :制造业企业的必备管理神器-ERP-企业唯一的盈利入口:销售管理

目录 一、ERP销售管理:卖产品 1.1 概述 1.2 核心功能 1. 客户管理: 2. 销售订单管理:最重要的功能**** 3. 销售发货管理: 4. 销售退货管理: 5. 销售统计分析: 二、用友ERP销售管理 2.1 概述 2.2…

UniApp 开发微信小程序教程(一):准备工作和环境搭建,项目结构和配置

文章目录 一、准备工作和环境搭建1. 安装 HBuilderX步骤: 2. 注册微信开发者账号步骤: 3. 创建 UniApp 项目步骤: 二、项目结构和配置1. UniApp 项目结构2. 配置微信小程序修改 manifest.json修改 pages.json 3. 添加首页文件index.vue 示例&…

Lynred在欧洲防务展上将展出新品——“HOT”红外传感器Seegnus。

Lynred在即将举办的巴黎欧洲防务展上将展出其令人瞩目的新品——“HOT”红外传感器Seegnus。这款专为战术视觉设计的大型阵列传感器,以其紧凑的封装和高分辨率的中波红外成像能力,无疑将为航空航天、国防和商业市场带来革命性的突破。 Seegnus传感器拥有…

【经典算法】LeetCode 8. 字符串转换整数 (atoi)(Java/C/Python3/Go实现含注释说明,Easy)

作者主页: 🔗进朱者赤的博客 精选专栏:🔗经典算法 作者简介:阿里非典型程序员一枚 ,记录在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法(公众号同名) ❤️觉得文章还…

蔚来汽车AI算法工程师,如何理解注意力?

大家好啊,我是董董灿。 今天分享一个上海蔚来汽车的AI算法岗位面试经验总结帖,面试岗位为算法工程师。 这次面试提到的问题,除了与实习相关内容和反问之外,面试官总共问了8个问题,主要集中在深度学习基础概念的理解上…

裁员裁到大动脉,是一种什么体验!

大家好啊,我是董董灿。 降本增效是每个当老板的人都喜欢挂在嘴边的口头禅,尤其是行业不景气,公司发展遇到瓶颈的时候。 大部分公司降本增效的手段其实非常相似,比较容易实施的手段也就那几种。 要么搞设备自动化和流程自动化&a…

Ubuntu 22.04.1 安装ubuntu有道词典时错误发生

1. Ubuntu环境版本 Linux lipan-Precision-T1700 6.5.0-26-generic #26~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Mar 12 10:22:43 UTC 2 x86_64 x86_64 x86_64 GNU/Linux 2. 有道词典 下载ubuntu系统的deb安装包。 网易有道翻译-支持文本翻译、文档翻译、AIBox英文写作、智…