深入理解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.hqwc.cn/news/688903.html

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

相关文章

【数据结构】二叉树(Binary Tree)

文章目录 一、树的概念及结构二、二叉树的概念及结构1.二叉树的概念2.特殊的二叉树3.二叉树的性质 三、二叉树的存储顺序存储链式存储 四、二叉树的实现1.创建二叉树2.二叉树的遍历前序遍历中序遍历后序遍历层序遍历根据遍历顺序创建二叉树 3.二叉树的基本操作1.总结点个数2.二…

国内运营商选择爱立信,或因它的低频5G技术更先进,价格更便宜

国内某运营商将大笔5G设备订单交给爱立信,引发了掀然大波,影响仍在扩散,对此各方说什么原因都有,笔者认为爱立信此次斩获大单,可能在于它的低频5G设备更先进,价格更便宜,对于急于降低成本的国内…

融知财经:期货和现货的区别是什么?哪个风险大?

期货和现货在交易对象等方面存在明显的区别。期货交易是一种衍生金融工具,主要用于价格发现、风险管理和投机,而现货交易则是商品和服务的实际买卖。在选择进行期货交易还是现货交易时,投资者需要根据自己的需求和市场情况来决定。 期货和现货…

智能驱动,精准管理:打造高效干部管理系统

干部管理系统是现代组织管理中不可或缺的工具,它通过信息技术的应用,提高了干部管理的效率和准确性。干部管理系统的主要功能包括: 1. 信息管理:系统可以存储和管理干部的个人信息,包括基本资料、工作经历、教育背景、…

Gitlab搭建

Gitlab搭建 一.gitlab安装二.gitlab安装目录三.gitlab常用命令汇总四.gitlab访问测试1.登录gitlab2.配置默认访问密码3.登陆自己创建的账户 五.gitlab图形化操作1.gitlab关闭自动注册2.gitlab免密登录3.gitlab创建项目4.使用管理员创建账户5.gitlab 添加邮箱设置5.1开启SMTP功能…

基于Python的数据分组技术:将数据按照1, 2, 3规则分为三个列表

目录 一、引言 二、数据分组原理与意义 三、案例分析 四、代码实现与解释 五、对新手友好的解释 六、技术细节与扩展 七、实际应用场景 八、总结 一、引言 在数据处理和分析的广阔领域中,数据分组是一项基础且重要的任务。数据分组通常指的是将数据集中的元…

Mac idea gradle解决异常: SSL peer shut down incorrectly

系统:mac 软件:idea 解决异常: SSL peer shut down incorrectly 查看有没有安装 gradle -v安装 根据项目gradle提示安装版本 brew install gradle7idea的配置 在settings搜索gradle,配置Local installation,选择自己的安装目录…

VS远程调试

因为是做工厂应用的客制化项目,在客户现场出现异常,本地又很难复现,而且重启软件可能又自动恢复了,此时可以用VisualStudio自带的远程调试功能进行调试,不需要重启软件,能较好的定位问题。客户电脑上不需要…

代码随想录第四十九天|买卖股票的最佳时机Ⅲ、买卖股票的最佳时机Ⅳ

题目链接:. - 力扣(LeetCode) 题目链接:. - 力扣(LeetCode) 买卖股票的最佳时机Ⅲ、买卖股票的最佳时机Ⅳ两道题目非常相似,只是次数为已知的两次与k次的区别,而与买卖股票的最佳时…

Python读取base64图片数据

Python读取base64图片数据 在Python中,可以使用base64模块来解码Base64编码的字符串,并将其转换回原始的数据(如图片)。但通常,Base64编码的图片字符串会带有前缀(如data:image/jpeg;base64,)&…

按键配合LDO实现开关功能

今天给大家分享一个学到的按键开关电路,适合没有足够空间给自锁开关的场景,既可以用于USB供电控制也可以用于电池供电控制。话不多说上电路图先。 核心任务就是通过按键控制LDO芯片的使能管脚的电平状态,这枚NCP芯片高电平使能,VB…

【计算机网络】物理层传输介质 习题3

双绞线是用两根绝缘导线绞合而成的,绞合的目的是( )。 A.减少干扰 B.提高传输速度 C.增大传输距离 D.增大抗拉强度 在电缆中采用屏蔽技术带来的好处主要是( ) A.减少信号衰减 B. 减少电磁干扰辐射 C.减少物理损坏 D. 减少电缆的阻抗 利用一根同轴电缆互连主机构成…