深入理解JavaScript事件循环Event Loop:宏任务与微任务的奇幻之旅

在这里插入图片描述

🔥 个人主页:空白诗

在这里插入图片描述

文章目录

  • 🎉 引言
    • 🌟 什么是事件循环?
    • 📚 「宏任务」 vs 「微任务」
      • 「宏任务」(Macrotask)
      • 「微任务」(Microtask)
      • 实际应用中的注意事项
    • 🔀 执行流程概览
    • 📝 代码示例
    • 🔍 深入理解「微任务」执行时机
      • 「微任务」的执行流程特点
      • 示例代码加深理解:
    • 💡 实践建议
      • 1. 避免过度依赖「微任务」
      • 2. 控制异步逻辑复杂度
      • 3. 注意异步操作对UI更新的影响
      • 4. 性能监控与调试
    • 🎖 总结
    • 🔗 相关链接

🎉 引言

JavaScript的世界里,一切皆为异步编程的魔法所驱动,而事件循环(Event Loop)正是这魔法背后的神秘引擎🔍。它确保了我们的代码能够高效、有序地执行,即使面对复杂的异步操作也能游刃有余。本文将带你深入探索这一机制,通过理论结合实践,揭开「宏任务」(Macrotasks)与「微任务」(Microtasks)的神秘面纱✨。


🌟 什么是事件循环?

事件循环是JavaScript运行时环境(如浏览器或Node.js)中用于协调执行代码的一种机制。它负责管理和执行两种类型的任务队列:「宏任务」(Macrotasks)和「微任务」(Microtasks),以确保异步操作能够有序且高效地完成,同时保证了代码的非阻塞执行特性。

在这里插入图片描述

具体来说,事件循环的工作流程遵循以下步骤:

  1. 执行全局脚本代码:当JavaScript引擎开始执行一段脚本时,它首先会执行所有的同步代码,这些代码被视为当前「宏任务」的一部分。

  2. 检查「微任务」队列:在当前「宏任务」执行完毕后,事件循环会查看「微任务」队列。如果有待处理的「微任务」,事件循环会清空整个「微任务」队列,即连续执行队列中的所有「微任务」,直到队列为空。这一过程会在每次「宏任务」结束后重复,确保「微任务」能够迅速得到响应。

  3. 执行宏任务:在处理完当前所有「微任务」后,事件循环会从「宏任务」队列中取出下一个任务并执行。这包括像setTimeoutsetInterval的回调、I/O操作完成、UI渲染等。

  4. 重复循环:一旦当前「宏任务」执行完毕,事件循环会再次检查是否有新的「微任务」需要处理,如此反复循环,直至「宏任务」队列和「微任务」队列均为空,或者有外部中断(如用户交互)发生。

事件循环的关键在于它确保了异步操作能够插入到同步代码执行的间隙中,既不会阻碍同步代码的执行,又能及时响应各种事件和数据变化,是实现JavaScript异步编程模型的基础。


📚 「宏任务」 vs 「微任务」

JavaScript的异步编程模型中,任务被划分为两大类:宏任务(Macrotasks)微任务(Microtasks)。这两者在事件循环中的处理方式和执行时机有着本质的不同,深刻理解它们对于编写高性能和可预测性的异步代码至关重要。

「宏任务」(Macrotask)

定义与特点:

  • 「宏任务」代表了执行环境中较重的异步操作,它们由宿主环境(如浏览器或Node.js)直接控制。
  • 这些任务通常涉及I/O操作、用户交互(如点击事件)、UI渲染以及基于定时器的功能(setTimeoutsetInterval)。
  • 每次事件循环的迭代中,最多只会执行一个「宏任务」 ,执行完毕后,事件循环会检查是否存在待处理的「微任务」

常见例子:

  • DOM事件处理(如点击、加载事件)
  • setTimeoutsetInterval 的回调
  • I/O 操作(文件读写)
  • UI渲染

「微任务」(Microtask)

定义与特点:

  • 相比之下,「微任务」更为轻量级,它们通常由JavaScript引擎自身管理,用于执行一些需要快速响应的操作。
  • 「微任务」在当前「宏任务」 结束后立即执行,且在下一个「宏任务」 开始前必须全部执行完毕,这使得「微任务」具有更高的优先级。
  • 在一个「宏任务」 执行期间,可以生成多个「微任务」,这些「宏任务」会在当前「宏任务」结束后形成一个队列,并一次性执行完毕。

常见例子:

  • Promise 的回调(.then.catch.finally
  • MutationObserver 的回调
  • queueMicrotask(在某些环境中提供,作为「微任务」的直接API)
  • Node.js 中的 process.nextTick

实际应用中的注意事项

  • 性能考量:由于微任务在同一个「宏任务」内连续执行,应避免在「微任务」中执行大量计算或产生大量新的「微任务」,以防调用栈过深导致性能问题。
  • 任务调度:合理安排「宏任务」「微任务」的使用,以控制代码执行的时机和顺序,特别是在处理异步逻辑时。

理解「宏任务」「微任务」的执行模型,可以帮助开发者更好地设计异步流程,避免因执行时机不当引发的bug,提升应用的响应速度和用户体验。


🔀 执行流程概览

  1. 全局脚本开始执行:当JavaScript引擎开始运行时,它会先执行全局脚本的同步代码部分。这是每个事件循环的起点,包括变量声明、函数定义等。。
  2. 执行当前「宏任务」:在执行过程中遇到诸如setTimeoutsetIntervalI/O请求或用户交互事件(在它们实际触发时),它们的处理逻辑会被安排到未来的「宏任务」队列中,而不是立即执行。同时,如果遇到新创建的Promise并且其内部有.then.catch,这些回调会被加入「微任务」队列
  3. 执行所有可执行的「微任务」:在当前「宏任务」执行完毕后,会检查「微任务」队列(这包括了Promise回调、MutationObserver回调等),「微任务」队列中的所有任务会在这个阶段被连续执行,直到队列为空,然后再继续下一轮事件循环。
  4. 渲染页面(如果有必要):在所有「微任务」执行完成后,浏览器判断是否需要重新渲染页面。如果DOM结构、CSS样式等有变化,此时可能触发UI更新。。
  5. 检查新的「宏任务」:一旦「微任务」队列 清空且完成可能的渲染工作,事件循环会检查是否有新的「宏任务」到达(例如来自网络的响应、定时器到期),并开始执行下一个「宏任务」的同步代码部分。整个过程形成了一个循环,不断迭代,直到「宏任务」队列为空且没有新的任务加入,此时JavaScript运行环境可能会进入休眠状态,等待新的事件触发。

在这里插入图片描述


📝 代码示例

下面的代码将帮助你直观理解「宏任务」「微任务」的执行顺序:

console.log('Start'); // 1. 同步代码首先执行setTimeout(() => {console.log('setTimeout'); // 4. 宏任务,最后执行
}, 0);new Promise((resolve) => {console.log('Promise'); // 2. 同步代码块内的Promise立即执行resolve();
}).then(() => {console.log('Promise.then'); // 3. 微任务,在当前宏任务的末尾执行
});// 输出顺序: Start -> Promise -> Promise.then -> setTimeout

这段代码展示了JavaScript「宏任务」(Macrotask)与「微任务」(Microtask)的执行顺序。下面是详细的解释,配合代码注释:

console.log('Start'); // 1. 同步代码首先执行
  • 同步代码执行:首先,JavaScript会执行全局脚本中的同步代码,输出"Start"。
setTimeout(() => {console.log('setTimeout'); // 4. 宏任务,最后执行
}, 0);
  • setTimeout 宏任务调度:接着,遇到setTimeout函数,它安排了一个「宏任务」到任务队列。虽然延迟时间设为0,但这不代表立即执行,而是意味着至少在当前脚本执行完成后,且最早在下一次事件循环开始时执行。因此,"setTimeout"的输出会晚于「微任务」
new Promise((resolve) => {console.log('Promise'); // 2. 同步代码块内的Promise立即执行resolve();
}).then(() => {console.log('Promise.then'); // 3. 微任务,在当前宏任务的末尾执行
});
  • Promise构造函数执行:创建并立即执行Promise构造函数中的代码,输出"Promise"。这是同步执行的一部分。

  • Promise.then 微任务调度:当Promise被解析(这里通过resolve()方法),其关联的.then方法中的回调会被加入到「微任务」队列。因为「微任务」总是在当前「宏任务」的最后执行,所以在"Promise"之后,紧接着输出"Promise.then"。

总结输出顺序: Start -> Promise -> Promise.then -> setTimeout

这清晰地表明了JavaScript运行时的事件循环机制:先执行同步代码,然后在每个「宏任务」结束前,会执行完所有的「微任务」。之后,才会继续下一个「宏任务」


🔍 深入理解「微任务」执行时机

「微任务」总是在当前「宏任务」结束后,下一个「宏任务」开始前执行。这意味着,如果你在一个「宏任务」的执行过程中不断地生成新的「微任务」,它们会继续累积,并在当前「宏任务」结束前全部执行完毕,而不是等待到下一轮事件循环。

「微任务」的执行流程特点

  1. 即时性「微任务」在当前执行栈清空后立即执行,这意味着它们不会受到后续「宏任务」的延迟影响。
  2. 连续执行:如果在执行一个「微任务」的过程中又产生了新的「微任务」,这些新产生的「微任务」不会等待当前循环结束,而是在当前「宏任务」「微任务」队列中继续累积,并在当前「宏任务」结束前依次执行完毕。这一过程会持续到「微任务」队列为空。
  3. 可能导致“任务饿死”:极端情况下,如果不断产生「微任务」而不让 JavaScript 引擎有机会处理「宏任务」,可能会导致浏览器或Node.js环境中的其他任务长时间得不到执行,表现为界面冻结或服务器响应迟缓等问题,这种情况称为“任务饿死”。

示例代码加深理解:

console.log('Start');function createMicrotask() {Promise.resolve().then(() => {console.log('Microtask');createMicrotask(); // 递归创建新的微任务});
}createMicrotask();console.log('End');

在这个例子中,createMicrotask函数通过Promise的.then方法创建了一个「微任务」。该函数在被调用时会打印"Microtask",并且递归调用自身再次创建一个新的「微任务」。由于「微任务」会在当前「宏任务」结束前全部执行完毕,因此在"End"被打印之后,控制台会连续输出多行"Microtask",直到达到JavaScript引擎的调用栈限制或其他停止条件为止。这进一步说明了「微任务」的即时执行和累积效应。


💡 实践建议

1. 避免过度依赖「微任务」

  • 使用场景选择:明确区分哪些操作更适合放在「微任务」中执行,如DOM更新后的立即操作、异步操作的快速回调等。对于需要一定延迟或不急于立即执行的任务,则可以考虑放入「宏任务」队列
  • 递归与循环控制:如示例代码所示的递归「微任务」调用,务必设置合理的退出条件,防止无限递归导致堆栈溢出。

2. 控制异步逻辑复杂度

  • 模块化和分层:将复杂的异步逻辑拆分为多个小的、职责单一的函数或模块,每个部分专注于处理一类任务,这样既便于阅读也易于维护。
  • 使用Async/Await:在支持的环境中,使用async/await语法糖可以极大地简化异步代码的编写,使其更像同步代码,提高可读性。

3. 注意异步操作对UI更新的影响

  • UI更新策略:虽然「微任务」先于UI渲染执行,但频繁的DOM操作或样式更改仍可能引起页面重排和重绘,影响性能。尽量批量处理DOM修改,或者利用requestAnimationFrame来协调UI更新,确保动画流畅。
  • 用户交互响应:确保关键的用户交互逻辑(如点击事件处理)能够迅速响应,避免在这些处理中嵌套过多「微任务」,以免阻塞UI线程,造成应用无响应的感觉。

4. 性能监控与调试

  • 工具利用:利用Chrome DevTools等开发者工具的Performance面板监控应用运行时的「宏任务」「微任务」调度情况,识别潜在的性能瓶颈。
  • 压力测试:在不同的设备和网络环境下进行压力测试,模拟高负载场景,检查「微任务」是否导致了不可接受的延迟或性能下降。

遵循这些建议,可以帮助开发者有效地平衡异步处理的需求与性能优化,确保应用程序的健壮性和用户体验。


🎖 总结

  • JavaScript事件循环是其异步编程模型的基础,它确保了非阻塞IO操作和高并发能力,让浏览器或Node.js环境下的JavaScript应用能高效运行。

  • 宏任务(Macrotasks),如setTimeoutsetIntervalI/OUI rendering等,构成了事件循环的主干,它们在每次循环的开始执行,控制着程序的主要流程。

  • 微任务(Microtasks),例如Promise的回调(then/catch/finally)、MutationObserverprocess.nextTick(在Node.js中)等,紧随当前「宏任务」之后执行,且在下一次「宏任务」开始前尽可能多地清空「微任务」队列,这使得「微任务」适用于需要立即响应的场景。

理解并正确利用「宏任务」「微任务」的执行机制,对于避免常见的异步陷阱、优化应用性能、以及实现流畅的用户体验至关重要。无论是处理复杂的异步逻辑、提升UI交互的即时反馈,还是维护代码的可读性和可维护性,掌握这一核心知识都能让你的JavaScript编程技能更上一层楼。

通过本文,希望你已经掌握了JavaScript事件循环的核心概念,理解了「宏任务」「微任务」的区别及其执行顺序。记住,掌握事件循环机制对于编写高效、可预测的异步JavaScript代码至关重要。现在,你已经具备了驾驭JavaScript异步魔法的能力,去创造更流畅的用户界面和强大的Web应用吧!🌈


🔗 相关链接

  • JavaScript 中的 Class 类
  • JavaScript中call、apply与bind的区别
  • JavaScript 垃圾回收机制深度解析:内存管理的艺术

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在JavaScript/Vue中获取当前时间并格式化输出(精确到时分秒)

如何在JavaScript/Vue中获取当前时间并格式化输出(精确到时分秒) 不只是树,人也是一样,在不确定中生活的人,能比较经得起生活的考验,会锻炼出一颗独立自主的心。在不确定中,就能学会把很少的养分…

在uniapp中如何安装axios并解决跨域问题

目录 1、安装axios 2、导入 3、使用(发请求) 2.解决跨域问题 1.为什么要解决跨域问题? 2.前端如何解决跨域问题? 1、安装axios npm install axios 2、导入 在main.js中导入使用 import axios from axios; // 创建一个名…

智慧互联,统信UOS V20桌面专业版(1070)解锁办公新模式丨年度更新

从小屏到大屏 突破,就在方寸之间 从人机到智脑 融合,旨在新质生产力 统信UOS一直致力于将先进科技与用户场景相结合,不断提升用户的工作效率和生产力。在最新发布的统信UOS V20桌面专业版(1070)版本中,我们…

Rust 解决循环引用

导航 循环引用一、现象二、解决 循环引用 循环引用出现的一个场景就是你指向我,我指向你,导致程序崩溃 解决方式可以通过弱指针,而Rust中的弱指针就是Weak 在Rc中,可以实现,对一个变量,持有多个不可变引…

【一支射频电缆的诞生】GORE 戈尔

工具连接: https://microwave-cablebuilder.gore.com/ 控制参数: 连接器: 欣赏

Android APP读写外置SD卡无权限 java.io.IOException: Permission denied

在物联网应用里,app需要对挂载SD卡读写文件,从 Android 4.4(KitKat)版本开始,Google 引入了一项名为 "Storage Access Framework" 的新功能,该功能限制了应用对外部存储的直接读写权限,要不然就是…

【挑战30天首通《谷粒商城》】-【第一天】03、简介-分布式基础概念

文章目录 课程介绍 ( 本章了解即可,可以略过)1、微服务简而言之: 2、集群&分布式&节点2.1、定义2.2、示例 3、远程调用4、负载均衡常见的负裁均衡算法: 5、服务注册/发现&注册中心6、配置中心7、服务熔断&服务降级7.1、服务熔断7.2、服务降级 8、AP…

Appium测试之获取appPackage和appActivity

appPackage和appActivity 进行appium自动化测试非常重要的两个参数,我们所测试的APP不同,这两个参数肯定也是不一样的。那如何快速的获取这APP的这两个参数呢?我这里介绍两个方法。 import org.openqa.selenium.remote.DesiredCapabilities;i…

从零开始学AI绘画,万字Stable Diffusion终极教程(六)

【第6期】知识补充 欢迎来到SD的终极教程,这是我们的第六节课,也是最后一节课 这套课程分为六节课,会系统性的介绍sd的全部功能,让你打下坚实牢靠的基础 1.SD入门 2.关键词 3.Lora模型 4.图生图 5.controlnet 6.知识补充 …

利用生成式AI重新构想ITSM的未来

对注入 AI 的生成式 ITSM 的需求,在 2023 年 Gartner AI 炒作周期中,生成式 AI 达到预期值达到顶峰后,三分之二的企业已经将生成式 AI 集成到其流程中。 你问为什么这种追求?在预定义算法的驱动下,IT 服务交付和管理中…

PyCharm 集成 Git

目录 1、配置 Git 忽略文件 2、定位Git 3、使用pycharm本地提交 3.1、初始化本地库 3.2、添加到暂存区 3.3、提交到本地库 3.4、切换版本 4、分支操作 4.1、创建分支 4.2、切换分支 4.3、合并分支 5、解决冲突 1、配置 Git 忽略文件 作用:与项目的实际…

【redis】Redis五种常用数据类型和内部编码,以及对String字符串类型的总结

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

51单片机入门:蜂鸣器

蜂鸣器介绍 蜂鸣器是一种将电信号转换为声音信号的器件,常用来产生设备的按键音、报警音等提示信号。 蜂鸣器的种类 1、从结构上:压电式蜂鸣器和电磁式蜂鸣器。 压电式蜂鸣器:通过压电陶瓷的压电效应原理工作的。当加有交变电压时&#xf…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-14-主频和时钟配置

前言: 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

轮式机器人简介

迄今为止,轮子一般是移动机器人学和人造交通车辆中最流行的运动机构。它可达到很高的效率, 如图所示, 而且用比较简单的机械就可实现它的制作。 另外,在轮式机器人设计中,平衡通常不是一个研究问题。 因为在所有时间里,轮式机器人一般都被设计成在任何时间里所有轮子均与地接…

idea上如何新建git分支

当前项目在dev分支,如果想在新分支上开发代码,如何新建一个分支呢?5秒搞定~ 1、工具类选择git,点击New Branch 或者右下角点击git分支,再点击New Branch 2、在弹出的Create New Branch弹窗中,输入你的新分支…

VMware 不能拍摄快照

问题: 拍摄快照后,会出现这个弹窗,然后虚拟机就直接自动退出了,还会弹出一个框: 解决方法: 我用的是 window11 和 VMware16.0.0 这是因为VM16与window11,二者之间版本不兼容问题,可…

Python 全栈系列243 S2S flask_celery

说明 按现有的几个架构部件,构建数据流。 S Redis Stream。这个可以作为缓冲队列和简单任务队列,速度非常快,至少是万条/秒的速度。 Q RabbitMQ。这个作为任务队列,消息也主要是元数据。读速比较慢,但有一些特性&a…

Linux(openEuler、CentOS8)常用的IP修改方式(文本配置工具nmtui+配置文件+nmcli命令)

----本实验环境为openEuler系统<以server方式安装>&#xff08;CentOS类似&#xff0c;可参考本文&#xff09;---- 一、知识点 &#xff08;一&#xff09;文本配置工具nmtui(openEuler已预装) nmtui&#xff08;NetworkManager Text User Interface&#xff09;是一…

【LeetCode算法】389. 找不同

提示&#xff1a;此文章仅作为本人记录日常学习使用&#xff0c;若有存在错误或者不严谨得地方欢迎指正。 文章目录 一、题目二、思路三、解决方案 一、题目 给定两个字符串 s 和 t &#xff0c;它们只包含小写字母。字符串 t 由字符串 s 随机重排&#xff0c;然后在随机位置添…