javascript任务队列包含宏任务和微任务两套独立队列;每次事件循环仅执行一个宏任务,随后清空全部微任务;宏任务包括settimeout、i/o回调等,微任务包括promise.then等;ui渲染发生在宏任务之间。

JavaScript 任务队列不是“一个队列”,而是两套独立又协作的队列:宏任务队列和微任务队列。任务提取不是按时间戳或插入顺序简单排队,而是严格遵循事件循环的调度节奏——每次只取一个宏任务,但会清空全部微任务。
宏任务每次只取一个
宏任务包括 setTimeout、setInterval、I/O 回调、UI 渲染、事件处理函数(如 click)等。事件循环每轮仅从宏任务队列头部取出一个任务执行,无论队列里有多少个待处理的 setTimeout 或点击回调。
- 即使多个 setTimeout 都设为 0ms,它们也按注册顺序依次进入宏任务队列,但不会批量执行
- 执行完这个宏任务后,才会进入下一轮循环,再取下一个宏任务
- UI 渲染通常插在宏任务之间(即上一个宏任务结束、下一个宏任务开始前),属于宏任务级的“隐式任务”
微任务每次全部清空
微任务包括 Promise.then/catch/finally、MutationObserver 回调等。它们不参与“轮次竞争”,而是在每个宏任务执行完毕后立即、连续执行,直到微任务队列为空。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 哪怕新 Promise 在微任务执行中途被 resolve,其 then 回调也会被立即加入当前微任务队列尾部,并继续执行
- 不存在“只执行一个微任务就停”的情况;只要队列非空,就一直取、一直执行
- 这导致微任务具有高确定性优先级,也容易引发“微任务风暴”(比如无限递归 Promise)
任务来源决定队列归属
同一个异步操作,不同阶段可能进入不同队列。关键看回调注册时机和规范定义:
- setTimeout(fn, 0) → 宏任务队列(即使延时为 0,仍受最小延迟限制,且明确归类为宏任务)
- Promise.resolve().then(fn) → 微任务队列(只要 Promise 已决议,then 回调立刻入微队列)
- fetch().then(fn) → then 是微任务;但 fetch 本身触发的是宏任务级网络请求,回调由微任务承载
- click 事件回调 → 宏任务;但回调内 new Promise().then() → 立即入微任务队列
渲染是宏任务间的检查点
浏览器不是每次执行完微任务就重绘,而是在一个宏任务结束、下一个宏任务开始前,检查是否需要更新 UI。这个渲染步骤本身不排队,也不可手动调度,是事件循环的固定环节。
- 连续多个 Promise.then 不会触发多次渲染,因为它们都在同一宏任务之后执行
- 两个 setTimeout 回调之间必然有一次渲染(除非被 requestIdleCallback 或 CSS 动画等机制抑制)
- 用 requestAnimationFrame 注册的回调,在下次渲染前执行,属于宏任务范畴,但有特殊调度时机
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










