导读:本期聚焦于小伙伴创作的《JavaScript事件循环机制中宏任务与微任务到底如何执行顺序?》,敬请观看详情。浏览器主线程遇到setTimeout和Promise时,常让人困惑谁先打印。事件循环并非简单排队,而是每一轮先清空微任务队列再取一个宏任务。微任务包含Promise回调、queueMicrotask等,宏任务有定时器、UI渲染、网络回调。若微任务里持续添加新微任务,会阻塞宏任务执行,造成页面卡顿。理清这两者差异,才能写好异步代码,避免顺序错乱与性能问题。

JavaScript作为单线程语言,依靠事件循环来协调同步代码、异步回调与渲染工作。理解宏任务与微任务的划分,是掌握异步执行顺序的核心。很多诡异的打印结果,都源于对这一机制的误读。

JavaScript事件循环机制中宏任务与微任务到底如何执行顺序?

一、事件循环的基本结构

事件循环可以理解为浏览器或Node环境中的一个永久运行的调度器。它不断从任务队列中取出任务放到调用栈执行。由于JavaScript只有一个主线程,若不加区分地混合异步任务,就会导致定时器不准、Promise回调滞后等问题。

规范将异步任务分为宏任务(macrotask)与微任务(microtask)。每执行完一个宏任务,引擎不会立刻取下一个宏任务,而是先完整清空微任务队列,之后才可能进行渲染或取新宏任务。这种分层设计让高优先级更新能及时完成。

1.1 常见宏任务来源

宏任务包括setTimeoutsetIntervalsetImmediate(Node)、I/O回调、UI交互事件等。它们由宿主环境调度,间隔受系统负载影响。比如定时器最小延迟在浏览器中通常为4毫秒,嵌套过深还会被强制拉长。

下列代码展示了宏任务被延后到微任务之后的现象:

console.log('start');
setTimeout(function() {
  console.log('timeout');
}, 0);
Promise.resolve().then(function() {
  console.log('promise');
});
console.log('end');
// 输出顺序:start end promise timeout

1.2 微任务的产生方式

微任务主要来自Promise的then、catch、finally回调,以及queueMicrotaskAPI、MutationObserver。它们在本轮宏任务结束后立即、连续执行,且新产生的微任务也会在本次清空过程中被处理。

这意味着微任务具有“插队”能力。若微任务递归添加自身,会饿死宏任务,页面无法响应点击或渲染。

二、执行顺序的深度剖析

当脚本开始运行,整段同步代码本身就是一个宏任务。执行中遇到的微任务被推入队列,遇到的宏任务被推入对应队列。同步代码结束后,事件循环进入微任务检查点。

检查点会循环取出微任务并执行,直到队列为空。随后可能更新渲染,再取一个宏任务重复上述过程。因此同一次循环中,所有微任务总先于下一个宏任务。

2.1 多层嵌套示例

通过嵌套Promise可以更直观看到清空逻辑:

setTimeout(() => console.log('macro1'), 0);
Promise.resolve().then(() => {
  console.log('micro1');
  Promise.resolve().then(() => console.log('micro2'));
});
Promise.resolve().then(() => console.log('micro3'));
// 输出:micro1 micro3 micro2 macro1

上述代码中,micro1执行时添加的micro2仍属于同一轮微任务清空阶段,因此先于macro1打印。这种机制保证了Promise链不会跨越宏任务边界。

2.2 与渲染的关系

浏览器通常会在宏任务之间执行渲染。如果微任务过多,渲染会被推迟,造成视觉卡顿。因此在微任务中应避免重计算,必要时改用requestAnimationFrame这类由渲染阶段调度的宏任务。

类型示例清空时机
宏任务setTimeout, I/O每轮取一个
微任务Promise.then, queueMicrotask宏任务后全清

三、实际开发中的注意事项

在业务代码里,常有人用setTimeout(fn, 0)来模拟nextTick,其实Promise微任务更快且更准。但在需要让出主线程给渲染时,反而应该用宏任务。

另外,async函数中的await之后代码等价于Promise.then,属于微任务。因此await不会让出整轮循环,只是把后续切成微任务片段。

3.1 避免微任务死循环

下面的写法会冻结页面:

function loop() {
  queueMicrotask(loop);
}
loop();
// 微任务永远清不完,宏任务无法执行

正确做法是将循环拆为宏任务,或使用Web Worker转移计算压力。理解队列边界,才能写出可响应的应用。

3.2 调试技巧

Chrome devtools的Performance面板能显示任务类型。若发现长微任务块,可检查是否误把大量数据处理放在Promise链中。用console.log配合任务分类注释,也能快速定位顺序异常。

掌握宏任务与微任务,不只是应付面试,更能让异步流程可控、界面流畅。建议在复杂交互中主动标注任务类型,减少隐式顺序依赖。

JavaScriptevent_loopmicrotask修改时间:2026-08-02 12:39:23

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