宏任务队列是宿主环境维护的一组按插入顺序排队的异步回调容器,事件循环每轮仅取首个任务完整执行后才处理微任务;其来源包括script、setTimeout、I/O、UI事件等,不插队、不嵌套。

JavaScript 宏任务队列不是“一个先进先出的简单队列”,而是一组由宿主环境(浏览器或 Node.js)按插入顺序维护的异步回调容器;它本身不调度,只等待事件循环每轮从中取出**第一个**任务执行,且必须完整执行完毕,才会进入微任务处理阶段。
宏任务的来源与注册时机
宏任务并非 JS 引擎主动创建,而是通过宿主 API 注册后,由底层系统(如定时器线程、网络线程、UI 线程)在条件满足时推入队列:
- script:页面加载时整个顶层脚本是第一个宏任务,同步执行
- setTimeout/setInterval:延迟时间到后,回调被推入宏任务队列尾部,不插队
- UI 事件:click、input、scroll 等触发后,对应事件处理器作为宏任务入队
- I/O 回调:fetch 响应到达、XMLHttpRequest 完成、Node.js 的 fs.readFile 结束时,回调入队
- postMessage / MessageChannel:跨上下文消息送达后,onmessage 处理器作为宏任务加入
宏任务队列的执行规则
事件循环对宏任务队列的处理严格遵循“单次取一、执行到底、不嵌套、不中断”原则:
- 每轮事件循环仅从队首取出一个宏任务,无论其耗时多长,都必须执行完全部同步代码(包括内部调用、嵌套函数等)
- 执行中产生的新宏任务(如 setTimeout(() => {}, 0))一律追加到队尾,不会打断当前任务
- A 宏任务中触发 B 宏任务,B 不会“嵌套执行”,而要等 A 完全退出、所有微任务清空后,才轮到 B
- 浏览器环境下,一个宏任务执行结束后,通常会安排一次 UI 渲染(若 DOM 变更可见),但渲染本身不属于宏任务,而是事件循环的隐式阶段
宏任务与微任务的关键边界
宏任务队列和微任务队列是两个完全独立的结构,调度节奏截然不同:
立即学习“Java免费学习笔记(深入)”;
- 微任务(Promise.then、queueMicrotask、MutationObserver)在**每个宏任务结束后立即连续执行全部**,中间不穿插任何宏任务或渲染
- 宏任务每次只推进一个,哪怕微任务队列在执行中动态新增了更多微任务,也会继续执行直到清空
- Promise.then 永远不进宏任务队列;同样,requestAnimationFrame 是渲染前的特殊钩子,归类为宏任务但执行时机早于普通宏任务
- 多个 setTimeout(fn, 0) 并非“立刻执行”,它们按注册顺序排队,且需等待前序宏任务 + 全部微任务完成才能轮到
典型场景中的行为表现
理解宏任务队列机制,能解释常见开发现象:
- 用户点击按钮更新 DOM,紧接着用 setTimeout 读取该 DOM —— 往往读不到最新值,因为渲染尚未发生,而 setTimeout 是下一个宏任务
- 连续调用 10 次 setTimeout(() => console.log('x'), 0),输出顺序固定为 1~10,不会因某次执行快就提前
- 在 Promise.then 中再调用 setTimeout,该 setTimeout 回调一定排在当前轮次所有微任务之后,属于下一轮宏任务


















