结论是:执行顺序由“同步→微任务→宏任务”不可动摇链条决定;同步代码先执行,当前宏任务结束后立即清空全部微任务,再取下一个宏任务,如console.log(1);setTimeout(()=>console.log(2),0);Promise.resolve().then(()=>console.log(3));console.log(4)输出必为1→4→3→2。

直接说结论:别从定义背起,从执行顺序反推机制,用三类典型代码反复验证,再结合浏览器调试器看任务入队时机——这样学最快也最稳。
先盯住一个铁律:执行顺序就是学习地图
所有宏任务和微任务的混乱,都源于没真正记住这个不可动摇的链条:
- 同步代码(script整体)先执行,遇到异步就登记,不卡住主线程
- 当前宏任务一结束,立刻清空全部已排队的微任务(不是只执行一个,是“清空”)
- 微任务队列空了,才去取下一个宏任务(哪怕它早于微任务被注册)
记不住抽象描述?就死磕这道题:
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
输出一定是 1 → 4 → 3 → 2。把这串结果默写三遍,比抄十遍“宏任务包括setTimeout”有用得多。
动手拆解三类高频代码,比看十篇原理文管用
光看理论容易飘,必须亲手拆开真实代码块:
-
带嵌套 Promise 的代码:重点观察
.then()是什么时候进微任务队列的——不是resolve()调用时,而是执行到.then()这一行时才注册。所以new Promise(() => { console.log('a'); }).then(() => console.log('b'))中,'a' 是同步,'b' 是微任务 -
async/await 混搭 setTimeout 的代码:
await后面的语句本质是Promise.then(),属于微任务;而setTimeout无论延迟多少,都是宏任务。二者永远不可能“并行”,只存在先后 -
DOM 操作 + MutationObserver + setTimeout 的组合:比如先改
div.innerHTML,再用MutationObserver监听变化(微任务),再用setTimeout延迟读取(宏任务)。你会发现 DOM 更新后,observer 立刻触发,但 setTimeout 总是最后执行
用浏览器开发者工具“看见”任务调度
Chrome DevTools 的 Performance 面板 或 Recorder 功能能直观显示任务分类:
- 录制一段含
setTimeout和Promise.then的脚本运行过程 - 回放时开启 “Tasks” 和 “Microtasks” 图层,你会清楚看到:每个长条(Task)后面紧跟着一簇短条(Microtask)
- 点开任一 Task,能看到它对应哪段代码(比如 “setTimeout handler” 或 “script evaluation”)
这种可视化训练,比纯脑补事件循环图有效十倍。不需要画图,只要能一眼认出 DevTools 里哪个是宏、哪个是微,就算入门成功。
避开三个新手必踩的坑
这些错误在真实项目里高频出现,提前识别就能少 debug 两小时:
-
以为 await 是“暂停整个函数”:错。
await只让出控制权给事件循环,函数剩余部分被包装成微任务排队,不是挂起等待 -
在 Promise 构造器里写 setTimeout 并期待它变微任务:错。
setTimeout内部无论怎么嵌套,它自己永远是宏任务;只有.then()、queueMicrotask()才能主动加微任务 -
用 setInterval 模拟“实时刷新”,却在回调里更新状态又立即读取:错。因为状态更新可能触发 Vue/React 的响应式更新(微任务),而你读取的时机如果在下一轮宏任务里,就拿不到最新值——应改用
queueMicrotask或确保读取也在同一微任务链中


















