如何理解JavaScript的宏任务与微任务?

来源:语言推理作者:赵六头衔:草根站长
导读:本期聚焦于小伙伴创作的《如何理解JavaScript的宏任务与微任务?》,敬请观看详情。浏览器事件循环里,同步代码执行完之后,队列里的回调为什么有的先跑有的后跑?这背后就是宏任务与微任务的调度差异。宏任务包括script整体、setTimeout、setInterval、I/O等,微任务涵盖Promise.then、MutationObserver等。每轮事件循环先执行一个宏任务,再清空所有微任务,然后渲染并更新UI。理解这套机制能解释为何Promise回调总快于定时器,也能帮你避开嵌套定时器的执行顺序坑。本文用代码对比和原理拆解,说清两者的入队时机与优先级关系。

JavaScript是单线程语言,为了不阻塞主线程,运行时把异步回调拆成了宏任务与微任务两类队列。事件循环每推进一轮,都会先取出一个宏任务执行,随后把当前微任务队列一次性清空,再去取下一个宏任务。这种分层调度直接决定了我们写异步代码时的执行顺序。

如何理解JavaScript的宏任务与微任务?

一、宏任务与微任务分别是什么

宏任务(macrotask)是由宿主环境发起的任务,常见的包括整段script代码、setTimeout、setInterval、setImmediate(Node环境)、I/O操作、UI渲染等。每当事件循环进入新阶段,就会从宏任务队列中取一个来执行。微任务(microtask)则由JavaScript引擎自身发起,典型代表是Promise的then、catch、finally回调,以及MutationObserver和queueMicrotask。

两者的核心区别在于优先级:微任务总是在当前宏任务结束后、下一个宏任务开始前执行。也就是说,一个宏任务运行期间产生的微任务,不会等到下一轮循环,而是立刻被依次处理完。这种机制让Promise比setTimeout更早拿到结果,也避免了频繁让出主线程导致的延迟。

1.1 常见任务分类表

类型具体示例发起方
宏任务script、setTimeout、setInterval、I/O浏览器或Node宿主
微任务Promise.then、MutationObserver、queueMicrotaskJS引擎

上表可以帮助你快速判断一段异步代码会进入哪个队列。实际编码时,如果希望某个回调尽快执行且不要被渲染或其他宏任务插队,优先使用Promise或queueMicrotask。

二、从代码看执行顺序

下面这段代码展示了宏任务与微任务混用时真实的输出顺序。我们在script这个最外层宏任务中,先后注册了微任务和宏任务,观察它们如何被调度。

console.log('start'); // 同步代码,属于首个宏任务

setTimeout(function() {
  console.log('timeout'); // 宏任务回调
}, 0);

Promise.resolve().then(function() {
  console.log('promise'); // 微任务回调
});

console.log('end'); // 同步代码

运行后控制台依次打印:start、end、promise、timeout。原因是整段script作为第一个宏任务执行,同步部分直接输出start和end;接着引擎发现微任务队列里有Promise回调,于是清空微任务输出promise;最后事件循环取下一个宏任务,也就是setTimeout的回调,输出timeout。

如果把setTimeout换成嵌套结构,或者在一个微任务里再派发微任务,规则依然成立:微任务队列会被完全清空,才会轮到下一个宏任务。这也解释了为什么连续await多个Promise不会让定时器插进来。

2.1 微任务中再产生微任务

Promise.resolve().then(function() {
  console.log('micro1');
  Promise.resolve().then(function() {
    console.log('micro2');
  });
});

setTimeout(function() {
  console.log('macro');
}, 0);

输出顺序是micro1、micro2、macro。可见在清空微任务时新入队的微任务也会在本轮被处理,而宏任务只能等待。这种“微任务优先且递归清空”的模型,是理解复杂异步流程的关键。

三、为什么需要这种分层设计

如果所有异步回调都放进同一个宏任务队列,那么像Promise这样需要高响应速度的API,就会被前面排队的I/O或定时器拖慢。微任务机制让引擎有机会在当前操作完成后立即续跑相关逻辑,不必等到浏览器渲染或下个时钟周期。

另一方面,若微任务没有限制地插队,又会导致宏任务饿死。因此规范限定每轮只取一个宏任务,但微任务要清空。这样既保证了交互流畅,也避免了定时器永远不执行。在Node和浏览器中具体实现略有差异,但调度原则一致。

3.1 实际开发中的注意点

  • 不要在微任务里写无限循环或大量同步计算,否则会阻塞宏任务直至卡死页面。
  • 需要精确控制节奏时,用setTimeout(fn, 0)把逻辑推到下一轮宏任务,避免和微任务纠缠。
  • async函数中的await后面代码,本质也是微任务,不要误以为它和setTimeout同时段。

掌握宏任务与微任务的边界,不仅能预测输出顺序,还能在写动画、批量DOM更新、接口并发时做出更合理的异步安排。

四、快速自检方法

遇到拿不准的执行顺序,可以在脑中模拟事件循环:先跑同步代码,再从左到右扫一遍产生的微任务并清空,最后才看定时器之类的宏任务。多写几段对比代码,很快就能形成直觉。

记住:一个宏任务搭配一轮微任务清空,就是JavaScript异步调度的最小节拍。

当你能稳定说出任意混合代码片段的输出,就说明已经真正理解了宏任务与微任务在JavaScript运行时的角色与协作方式。

JavaScript宏任务微任务修改时间:2026-08-06 02:24:25

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。