导读:本期聚焦于唐僧创作的《事件循环如何调度微任务与宏任务?执行顺序能预判吗?》,敬请观看详情。JavaScript 单线程模型意味着同一时刻只能执行一段代码,但浏览器又必须同时处理网络请求、定时器和用户交互等异步操作。事件循环正是协调同步代码与异步回调的核心机制。它管理三类执行单元:调用栈负责当前正在运行的函数,宏任务队列存放整体脚本、setTimeout、setInterval、I/O 回调等,微任务队列存放 Promise.then、MutationObserver、queueMicrotask 等。执行时,每次只取出一个宏任务运行,宏任务结束后会清空整个微任务队列,然后才进入渲染或下一个宏任务。本文通过多个代码实例,完整展示微任务与宏任务的执行顺序,包括 Promise 链、async/await、嵌套调用,以及浏览器渲染前的微任务时机。理解这套调度规则后,能够准确预判复杂异步代码的输出结果,也有助于理解 Vue 的 nextTick 实现原理。

事件循环(Event Loop)是 JavaScript 运行时中的核心调度机制,它决定了异步代码在何时被放入调用栈执行。虽然 ECMAScript 规范本身没有定义事件循环,但所有主流宿主环境都实现了相似模型:一个调用栈配合多个任务队列,循环检查调用栈是否为空,并按照优先级取出任务执行。要理解执行顺序,必须先分清三种结构:调用栈、宏任务队列、微任务队列。

事件循环如何调度微任务与宏任务?执行顺序能预判吗?

调用栈是 JavaScript 引擎执行代码的场所。例如一段脚本执行时,全局上下文首先入栈,函数调用再次入栈,函数返回后出栈。同步代码可以连续压栈出栈,而异步操作不会直接被放入调用栈,而是先在宿主环境中注册。当异步条件满足时,对应的回调会进入相应的任务队列,等待调用栈清空后被执行。这个过程就是事件循环的基本轮廓。

一、宏任务与微任务的分类依据

宏任务是事件循环中最基本的任务单元。一段整体脚本本身就是一个宏任务,此外还包括 setTimeout、setInterval、I/O 操作、UI 渲染等。宏任务的特点是按顺序从队列中取出一个执行,执行过程中可能产生新的宏任务或微任务,新产生的任务会被追加到队列末尾。

微任务是一类比宏任务优先级更高的异步任务。常见来源包括 Promise.then、Promise.catch、Promise.finally、queueMicrotask 以及浏览器中的 MutationObserver。微任务队列通常由 JavaScript 引擎直接维护,当当前宏任务执行完毕、调用栈清空后,引擎会立即清空微任务队列,直到队列为空才继续下一轮循环。

任务类型常见来源执行特征
宏任务整体 script、setTimeout、setInterval、I/O每次只执行一个,队列可能被新任务追加
微任务Promise 回调、queueMicrotask、MutationObserver当前宏任务结束后一次性清空所有微任务

从表中可以看出,宏任务与微任务并非简单的先后关系,而是存在嵌套和优先级差异。一个宏任务执行过程中,凡是调用 Promise.resolve().then(...)queueMicrotask(...),回调都会进入微任务队列。即使在这段代码后面还有另一个 setTimeout,微任务也一定先于下一个宏任务执行,因为清空微任务队列是每一轮循环的固定步骤。

二、执行流程与代码输出验证

标准的事件循环一轮可以概括为以下步骤:先执行一个宏任务;执行完该宏任务后,如果调用栈为空,则清空微任务队列;微任务清空后,浏览器会选择是否进行渲染;最后进入下一轮循环,取出下一个宏任务。这里需要特别强调:宏任务每次只执行一个,微任务每次清空整个队列。

下面这段代码可以验证这个规则:

console.log('script start');

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

Promise.resolve().then(function () {
    console.log('promise1');
}).then(function () {
    console.log('promise2');
});

console.log('script end');

输出结果是:script start、script end、promise1、promise2、setTimeout。原因在于整体脚本是第一个宏任务,执行时先同步打印 script start 和 script end。setTimeout 回调被注册为宏任务,进入宏任务队列。两个 Promise.then 回调进入微任务队列。当前宏任务结束后,引擎清空微任务队列,依次打印 promise1 和 promise2。之后才进入下一轮循环,执行 setTimeout 宏任务,打印 setTimeout。

如果微任务中继续产生新的微任务,执行顺序依然不会跳出清空阶段。例如一个 Promise.then 回调里再次调用 Promise.resolve().then,新产生的微任务会追加到当前微任务队列末尾,并在本轮内被执行。这是微任务与宏任务最显著的区别之一,也是某些异步递归可能导致页面长时间不渲染的原因。

三、Promise 和 async/await 中的顺序陷阱

async/await 本质上是对 Promise 的语法封装,但它会带来一些容易混淆的执行顺序。async 函数调用时,其内部代码会同步执行到第一个 await 表达式。await 后面的表达式会先求值,而 await 之后的代码会被包裹成微任务,等待当前同步代码执行完毕后进入微任务队列。

看下面的例子:

async function foo() {
    console.log('foo start');
    await bar();
    console.log('foo end');
}

async function bar() {
    console.log('bar');
}

console.log('global start');
foo();
console.log('global end');

输出顺序是:global start、foo start、bar、global end、foo end。调用 foo 时同步打印 foo start,然后执行 await bar(),bar 是 async 函数,会同步打印 bar 并返回一个已解决状态的 Promise。注意 await 之后的 console.log('foo end') 并不会立即执行,而是被放入微任务队列。此时 foo 函数暂停,控制权返回到全局作用域,继续打印 global end。当前宏任务结束后,微任务队列中的回调执行,打印 foo end。

另一个常见陷阱是 return await 与直接 return 的区别。return await promise 会比 return promise 多产生一次微任务并影响后续链式回调的顺序。虽然结果值可能相同,但在 try/catch 捕获错误、堆栈信息以及顺序敏感场景下存在差异。理解 await 总是会拆解一层 Promise 并让后续代码延后到微任务,是吃透 async/await 的关键。

四、微任务与浏览器渲染及 Vue nextTick 的关联

浏览器渲染并不是一个独立的任务队列,而是事件循环中的可选阶段。每轮循环在清空微任务之后,浏览器会判断当前是否需要重新渲染。如果需要渲染,会先执行 requestAnimationFrame 回调,再进行样式计算、布局和绘制。因此,微任务总是在渲染之前执行。这意味着如果在微任务中修改 DOM,用户几乎感觉不到中间状态。

Vue 的 nextTick 就是利用微任务这一特性来实现 DOM 更新后的回调。在数据变化时,Vue 并不会立即更新真实 DOM,而是将组件更新函数推入一个队列,并在下一个微任务中批量执行。nextTick 提供的回调会在组件更新完成后运行,因此开发者可以在回调中安全地访问已更新的 DOM。不同实现版本的回退策略不同,早期可能会使用 MutationObserver 或 setTimeout,目前主流版本优先使用 Promise.then,属于典型的微任务实现。

由于微任务会在下一次渲染前执行完毕,Vue 的异步更新可以让多次数据变更只触发一次 DOM 更新,从而提升性能。这也解释了为什么在修改数据后立即读取 DOM,获取到的可能还是旧值,而放在 nextTick 回调中就能读到新值。理解这一点,能帮助开发者避免许多响应式数据相关的时序问题。

综合以上分析,事件循环的执行顺序可以总结为:一个宏任务 → 清空所有微任务 → 可选渲染 → 下一个宏任务。微任务内部新增的微任务仍会在本轮清空,宏任务内部新增的宏任务则必须等待下一轮。掌握这个规则后,无论是 setTimeout 与 Promise 混合使用,还是 async/await 中的 await 后续代码,都能准确预判其执行顺序。

事件循环微任务宏任务修改时间:2026-08-24 18:28:09

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