JavaScript执行是单线程的,靠事件循环调度微任务(如Promise.then、queueMicrotask)和宏任务(如setTimeout、I/O),微任务在同步代码后立即清空执行,宏任务每次只执行一个且中间插入微任务检查。
JavaScript 的执行是单线程的,但靠任务队列机制实现异步行为。微任务(microtask)和宏任务(
mac
rotask)不是语法层面的概念,而是事件循环(Event Loop)中对任务分类调度的实际规则——理解它们的关键,在于知道哪些操作会进入哪类队列、谁先执行、以及为什么
总比
先触发。
哪些操作产生微任务?
微任务在当前同步代码执行完后、下一轮宏任务开始前立即执行,且会清空整个微任务队列(即连续执行,不穿插渲染或 I/O)。常见来源包括:
/
/
回调(即使
)
显式加入的回调
MutationObserver 的回调
后续代码(本质是 Promise 微任务链的一部分)
注意:
构造函数内的执行器(executor)是同步运行的,只有其内部的
/
调用才会触发后续微任务。
哪些操作产生宏任务?
宏任务每次只执行一个,执行完后检查并清空全部微任务,再取下一个宏任务。典型来源有:
立即学习
“
Java免费学习笔记(深入)
”;
/
(Node.
js
独有,非标准)
I/O 回调(如
在 Node.js 中)
UI 渲染(
浏览器
中,虽不可直接调度,但属于宏任务周期的一部分)
和
的消息处理
特别注意:
并不意味着“立刻执行”,它只是尽快把
推入宏任务队列——实际执行要等当前宏任务 + 所有微任务完成之后。
Comet AI 浏览器
下载 Comet AI 浏览器,体验由 Perplexity AI 驱动的革命性上网方式。内置 AI 助手可实时总结网页、跨标签页对比信息、自动执行任务。告别繁琐操作,让 AI 成为你的浏览副驾,大幅提升研究与工作效率。支持 Windows、macOS、Android 和 iOS。
下载
执行顺序如何确定?看一个典型例子
下面这段代码能清晰体现两者的嵌套关系:
输出顺序是:
→
→
→
→
→
。原因如下:
同步代码先执行:
和
然后清空微任务队列:两个
(
和
),其中第二个还触发了一个新的
(推入宏任务队列)
接着取下一个宏任务:
的
最后执行新推入的宏任务:
关键点:微任务可以递归产生新微任务(比如
里再返回
),它们都会被追加到当前微任务队列末尾,并在本轮全部执行完;而宏任务之间永远是串行的,中间必然插入微任务检查。
容易忽略的坑:async/await 与错误捕获
函数返回的是 Promise,
后面的表达式一旦 resolve/reject,后续代码会被包装进微任务。这意味着:
未被
包裹的
错误,不会同步抛出,而是变成 rejected Promise,需靠
或顶层
捕获
多个
连续写,它们之间是微任务衔接,不是同步栈;调试时断点可能“跳过”中间状态
会触发一次微任务调度,哪怕值已确定——这在性能敏感循环中可能成为隐性开销
真正难调试的,往往不是“谁先谁后”,而是微任务队列被意外延长(比如某个
里又创建了新 Promise 链),导致预期中的 DOM 更新延迟或状态不同步。
Promise.thensetTimeoutPromise.prototype.then.catch.finallyPromise.resolve().then(...)queueMicrotask()awaitPromiseresolverejectsetTimeoutsetIntervalsetImmediatefs.readFilepostMessageMessageChannelsetTimeout(fn, 0)fnconsole.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
Promise.resolve().then(() => {
console.log(4);
setTimeout(() => console.log(5), 0);
});
console.log(6);16342516Promise.then34setTimeoutsetTimeout25thenPromiseasyncawaittry/catchawait.catchunhandledrejectionawaitawait Promise.resolve()then