宏任务队列由宿主环境按来源分别维护,包括用户交互事件(如click)、script标签执行和定时器回调,每类按触发或注册顺序入队,每次事件循环仅执行一个,且须等其同步代码及后续微任务全部完成。

宏任务队列不是由 JavaScript 引擎统一创建的“一个队列”,而是由宿主环境(浏览器或 Node.js)按不同来源分别维护的一组有序容器。这些来源决定了回调何时、以何种方式进入宏任务队列,也直接影响代码执行时机和用户感知行为。
用户交互事件是典型的宏任务源
点击、滚动、键盘输入、表单提交等操作触发的事件处理函数,都会作为独立宏任务入队。它们不立即执行,而是在当前执行栈清空、微任务清空后,才被事件循环取出执行。
- 一次 click 触发的 handler 是一个宏任务,内部同步代码全部跑完才算结束
- 连续快速点击多次,会生成多个宏任务,按触发顺序排队,不会合并或跳过
- 在 handler 中修改 DOM,但渲染不会立刻发生——要等到该宏任务结束、微任务清空、浏览器主动安排下一帧渲染
script 标签执行本身就是首个宏任务
页面加载时,整个 <script> 块的同步代码构成第一个宏任务。它不是“启动器”,而是事件循环真正意义上的起点。</script>
- 全局作用域代码、模块顶层语句、IIFE 等都属于这个宏任务的一部分
- 它执行完毕后,才会检查是否有微任务(如 Promise 初始化产生的 then 回调)
- 后续所有异步逻辑(包括 setTimeout(…, 0))都必须等它彻底结束才能开始排队
定时器回调按注册顺序进入宏任务队列
setTimeout 和 setInterval 的回调,在延迟到期后被宿主环境推入宏任务队列尾部,不插队、不抢占。
- 多个 setTimeout(fn, 0) 注册后,即使时间相同,也严格按注册先后顺序执行
- 它们的执行时机取决于前序宏任务是否完成 + 微任务是否清空,而非“一到时间就运行”
- 如果前一个宏任务耗时很长(比如长循环),即使定时器已到期,回调仍需等待
理解这三类源的关键,在于意识到宏任务不是 JS 语言特性,而是运行时环境对“可感知工作单元”的建模方式。它们共同构成事件循环的驱动节奏,也决定了用户界面更新、数据响应和逻辑流转的真实时序。











