标签中的同步代码是宏任务,由html解析时按规范入队;其内部产生的promise.then、queuemicrotask等回调才构成微任务,事件循环在每个宏任务后清空微任务队列。

微任务和宏任务的执行顺序不是靠 HTML 写出来的,而是由 JavaScript 引擎在浏览器中按事件循环规则自动调度的;<script></script> 标签里写的 JS 代码本身就是一个宏任务,它触发的 Promise.then、queueMicrotask 等才会产生微任务。
为什么 script 标签里的代码是宏任务
浏览器解析 HTML 遇到 <script></script> 时,会把整个脚本块(从开始标签到结束标签之间的同步代码)当作一个初始宏任务入队。它不是“HTML 做的”,而是规范定义的行为——这个宏任务执行完后,引擎才检查微任务队列。
- 哪怕脚本是内联的、空的、或只有一行
console.log,它都算一个宏任务 - 多个
<script></script>标签 = 多个独立宏任务,按顺序执行,每个执行完都要清空一次微任务队列 - 动态插入的
<script src="..."></script>加载完成后,其回调也作为新宏任务入队
哪些 JS API 明确产生微任务
只有调用特定 JS 接口才会往微任务队列塞任务,HTML 本身不提供这类能力。常见且可靠的微任务来源有:
-
Promise.prototype.then/.catch/.finally的回调函数(只要 Promise 状态已确定) -
queueMicrotask(callback)—— 最干净的手动添加方式,现代浏览器全支持 -
MutationObserver的回调(监听 DOM 变化后触发) -
Observable(非标准,暂不推荐)
注意:setTimeout、setInterval、fetch().then(注意:fetch 本身是宏任务,但它的 .then 是微任务)、addEventListener 回调全是宏任务,别误判。
容易被忽略的渲染时机陷阱
微任务执行期间,浏览器**不会渲染**;而宏任务之间,浏览器可能插入一次 UI 渲染(比如在两个 setTimeout 之间)。这意味着:
- 连续 10 个
queueMicrotask会阻塞页面更新,用户看不到中间状态 - 想让 DOM 变更立刻可见?得让 JS 主线程“交还控制权”,比如用
setTimeout(() => {}, 0)或requestIdleCallback -
Promise.then里改了innerHTML,用户要等到整个微任务队列清空 + 下一个宏任务开始 + 渲染阶段才看到
真正难的不是记住“微任务在宏任务之后执行”,而是预判嵌套调用中微任务如何层层追加——比如一个 then 里又调用了 queueMicrotask,它会排在当前微任务队列末尾,但仍在本轮事件循环内执行完。这种链式累积最容易导致意料之外的卡顿或状态错乱。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











