0

0

事件循环中的“任务”和“作业”有什么区别?

畫卷琴夢

畫卷琴夢

发布时间:2025-08-08 10:48:02

|

489人浏览过

|

来源于php中文网

原创

宏任务和微任务的核心区别在于执行时机和优先级:宏任务是事件循环每轮执行一个的主线任务,如settimeout、i/o、ui事件;微任务则在当前宏任务结束后立即全部执行,如promise.then、queuemicrotask。2. 微任务优先级高于宏任务,必须清空微任务队列后才会进入下一宏任务,这直接影响代码执行顺序、ui响应速度和数据一致性,是前端性能优化和避免bug的关键机制。

事件循环中的“任务”和“作业”有什么区别?

事件循环中的“任务”(通常指宏任务,Macrotasks)和“作业”(通常指微任务,Microtasks)最核心的区别在于它们的执行时机和优先级。简单来说,宏任务是浏览器或Node.js环境在事件循环的每个“回合”中处理的较大、粒度较粗的工作单元,比如脚本执行、用户交互事件、网络请求回调等。而微任务则是更小、优先级更高的工作,它们会在当前宏任务执行完毕后,但在下一个宏任务开始之前,被全部清空并执行。你可以把它想象成:宏任务是主线任务,而微任务是当前主线任务完成后必须立即处理的“支线急件”,清完了才能接着做下一个主线任务。

事件循环中的“任务”和“作业”有什么区别?

解决方案

理解事件循环中任务(Macrotasks)和作业(Microtasks)的调度机制,对于编写高性能、响应迅速的JavaScript应用至关重要。我个人觉得,这玩意儿是前端进阶的必修课,搞不清楚就容易踩坑,比如UI卡顿、数据更新不及时等。

宏任务队列(Macrotask Queue) 宏任务代表了事件循环中的一个完整周期。常见的宏任务包括:

事件循环中的“任务”和“作业”有什么区别?
  • setTimeout()
    setInterval()
    的回调
  • setImmediate()
    的回调 (Node.js特有)
  • I/O 操作的回调 (如文件读写、网络请求)
  • UI 渲染事件 (浏览器环境)
  • 用户交互事件 (如点击、键盘输入)

当一个宏任务被添加到队列时,它会等待当前执行栈清空,并且微任务队列被完全清空后,才有可能被事件循环选中并执行。事件循环在每个“滴答”中只会处理一个宏任务。

微任务队列(Microtask Queue) 微任务则具有更高的优先级。它们会在当前正在执行的宏任务完成之后,但在事件循环去取下一个宏任务之前,被立即、全部执行。这意味着,如果在同一个宏任务中产生了多个微任务,它们会按顺序在当前宏任务结束后被一次性处理掉。常见的微任务包括:

事件循环中的“任务”和“作业”有什么区别?
  • Promise.then()
    ,
    Promise.catch()
    ,
    Promise.finally()
    的回调
  • MutationObserver
    的回调 (用于监听DOM变化)
  • queueMicrotask()
    的回调 (一个明确调度微任务的API)
  • Node.js 中的
    process.nextTick()
    (优先级甚至高于其他微任务,会在当前操作完成后立即执行,通常被认为是微任务队列的“头等公民”)

执行流程总结:

  1. 执行当前宏任务(例如,一个完整的脚本块)。
  2. 当前宏任务执行完毕后,检查微任务队列。
  3. 如果微任务队列不为空,则清空并执行所有微任务,直到队列为空。
  4. 执行UI渲染(仅限浏览器,且渲染时机通常在微任务清空后,下一个宏任务开始前)。
  5. 从宏任务队列中取出一个新的宏任务,重复步骤1。

这个循环往复的过程,保证了JavaScript的单线程特性,同时又提供了异步处理的能力。

为什么理解任务和作业的优先级对前端开发至关重要?

在我看来,搞清楚宏任务和微任务的优先级,直接关系到你代码的执行顺序、UI的响应速度以及数据的同步状态。有时候,我们遇到页面卡顿、动画不流畅,或者数据更新了但UI没及时响应,很可能就是对这个机制理解不到位导致的。

首先,它影响用户体验。比如,你有一个耗时的计算,如果你直接放在同步代码里,或者放在一个宏任务里但没有合理拆分,它会阻塞主线程,导致页面卡死,用户操作无响应,这就是所谓的“掉帧”或“卡顿”。但如果你能巧妙地利用

setTimeout(..., 0)
把它拆分成多个宏任务,或者利用微任务在不阻塞UI渲染的前提下尽快完成一些非视觉性的状态更新,用户感知到的流畅度会大大提升。

其次,它决定了代码的执行时序。尤其是当你混合使用Promise、setTimeout、DOM操作时,不清楚它们的优先级,很容易出现意想不到的bug。比如,你可能期望某个DOM更新立即生效,然后紧接着执行一个依赖这个更新的逻辑,但如果DOM更新被安排在下一个渲染周期,而你的后续逻辑在微任务中,那就会出问题。再比如,Promise链式调用中的

.then()
是微任务,它会比
setTimeout
里的代码先执行,即使
setTimeout
的延迟设为0。这种微妙的时序差异,是调试复杂异步逻辑时的关键线索。

最后,它关乎数据一致性。在某些场景下,你可能需要在一次事件循环中,确保所有相关的数据更新都完成后,才进行下一步操作。微任务的“立即执行”特性,使得它非常适合用于批处理一系列相关的状态更新,确保在UI渲染前数据已经完全就绪,避免显示中间状态或不一致的数据。

Calliper 文档对比神器
Calliper 文档对比神器

文档内容对比神器

下载

在实际开发中,如何利用任务和作业的特性优化代码?

实践中,我们确实可以利用宏任务和微任务的特性来优化代码,让应用表现得更“聪明”。这不仅仅是理论知识,更是解决实际问题的工具

一个常见的场景是避免长时间阻塞主线程。如果你有一个计算量巨大的函数,直接运行会卡住页面。这时,你可以把它拆分成小块,然后用

setTimeout(taskPart, 0)
把这些小块推迟到不同的宏任务中执行。这样,每次只占用主线程一小段时间,中间给浏览器机会去处理UI事件和渲染,页面就不会显得卡顿。这有点像把一个大任务“切片”,分批消化。

再比如,确保状态更新的及时性与原子性。在一些复杂的组件或数据流管理中,你可能需要在一系列异步操作(比如网络请求)完成后,一次性地更新多个相关联的状态。Promise的

.then()
链条就是微任务,它保证了所有
.then()
中的逻辑会在当前宏任务结束后、下次渲染前全部执行完毕。这对于需要同步DOM更新或者确保数据在渲染前完全一致的场景非常有用。比如,在一个数据更新后,你需要立即根据新数据调整DOM结构,那么把DOM操作放在Promise链的
.then()
里,就能保证在下一次浏览器重绘之前,这些操作已经完成。

我个人在使用Vue/React等框架时,也经常会遇到类似情况。框架内部的批量更新机制,很多时候就利用了微任务来收集多次状态改变,然后在当前宏任务结束时统一进行一次组件更新,而不是每次状态变动都触发一次昂贵的重新渲染。如果你想手动实现类似批处理效果,

queueMicrotask
这个API就非常直接好用,它能让你明确地将一个回调函数安排到微任务队列中。

// 示例:利用setTimeout避免阻塞UI
function performHeavyComputation() {
  let count = 0;
  const total = 1000000000;

  function processChunk() {
    const chunkSize = 100000;
    for (let i = 0; i < chunkSize; i++) {
      if (count >= total) {
        console.log("计算完成!");
        return;
      }
      // 模拟耗时计算
      Math.sqrt(count++);
    }
    // 将剩余部分推迟到下一个宏任务
    setTimeout(processChunk, 0);
  }

  processChunk();
}

// 示例:利用Promise确保立即更新
function updateDataAndUI() {
  console.log("1. 开始更新数据");
  Promise.resolve().then(() => {
    console.log("3. Promise微任务:更新数据成功,准备调整UI");
    // 假设这里进行了一些DOM操作
    document.body.style.backgroundColor = 'lightblue';
  });
  console.log("2. 同步代码继续执行");
}

// performHeavyComputation();
// updateDataAndUI();

上面这个例子里,

performHeavyComputation
通过
setTimeout(..., 0)
将大计算任务分解,避免长时间阻塞。而
updateDataAndUI
则展示了Promise的微任务特性:即使同步代码在Promise之后,微任务中的回调依然会在当前宏任务(即整个
updateDataAndUI
函数执行完毕)结束后立即执行,然后才轮到下一个宏任务。

事件循环的内部机制是怎样的,它如何调度任务和作业?

事件循环(Event Loop)是JavaScript运行时环境的核心,它决定了代码的执行顺序。它不是JavaScript语言本身的一部分,而是宿主环境(如浏览器或Node.js)提供的一个机制。理解它的内部运作,能帮助我们更深层次地把握异步编程。

从宏观上看,事件循环是一个永不停止的循环,它的主要职责就是不断地检查两个核心组件:调用栈(Call Stack)事件队列(Event Queue,即宏任务队列)。但更细致地看,还有微任务队列(Microtask Queue)的参与。

整个调度过程大致是这样的:

  1. 执行全局代码或当前宏任务: JavaScript引擎会首先执行所有同步代码,这些代码会被压入调用栈并执行。当一个函数被调用时,它会被推入栈顶;当它返回时,就会从栈中弹出。
  2. 调用栈清空: 当所有的同步代码执行完毕,调用栈变为空。这是事件循环开始发挥作用的信号。
  3. 处理微任务: 事件循环会立即检查微任务队列。如果队列中有任务,它会不间断地、一个接一个地执行所有微任务,直到微任务队列完全清空。这个过程是原子性的,意味着在清空微任务队列的过程中,不会有新的宏任务被执行,也不会有UI渲染发生。
  4. UI渲染(浏览器特有): 在浏览器环境中,当微任务队列清空后,浏览器可能会进行一次UI渲染。这个时机非常关键,它确保了所有由微任务(如Promise回调)引起的状态更新能在下一次屏幕绘制前完成。
  5. 处理宏任务: 渲染完成后(如果需要),事件循环会从宏任务队列中取出一个(注意,是“一个”)最老的任务,将其推入调用栈执行。
  6. 循环往复: 当这个宏任务执行完毕后,调用栈再次清空,事件循环又会回到步骤3,再次检查并清空微任务队列,然后是UI渲染,再取下一个宏任务,如此循环,永不停歇。

这就是一个完整的事件循环“滴答”(tick)。每次“滴答”都包含了一个宏任务的执行,以及紧随其后的所有微任务的清空。这种机制确保了高优先级的微任务能尽快得到响应,而低优先级的宏任务则需要排队等待。理解这个循环,就理解了为什么

Promise.resolve().then(...)
会比
setTimeout(..., 0)
先执行,因为它属于当前宏任务结束后立即处理的“急件”。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

399

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

575

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

525

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

525

2023.08.10

go语言 数组和切片
go语言 数组和切片

本专题整合了go语言数组和切片的区别与含义,阅读专题下面的文章了解更多详细内容。

46

2025.09.03

js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

515

2023.06.20

js获取当前时间
js获取当前时间

JS全称JavaScript,是一种具有函数优先的轻量级,解释型或即时编译型的编程语言;它是一种属于网络的高级脚本语言,主要用于Web,常用来为网页添加各式各样的动态功能。js怎么获取当前时间呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

245

2023.07.28

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

340

2023.08.03

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

54

2026.01.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Vue 教程
Vue 教程

共42课时 | 7.6万人学习

Vue3.x 工具篇--十天技能课堂
Vue3.x 工具篇--十天技能课堂

共26课时 | 1.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号