浏览器JavaScript事件循环是单线程+任务队列+渲染协调机制,宏任务每次执行一个,之后清空全部微任务;微任务在宏任务结束、下一轮开始前执行;渲染发生在宏任务之间,rAF在重绘前执行;示例输出为1→4→3→2。

浏览器环境下的 JavaScript 事件循环,核心是“单线程 + 任务队列 + 渲染协调”,它决定了代码何时执行、DOM 何时更新、异步操作如何回调。
宏任务和微任务的区分是关键
事件循环每次只从宏任务队列中取出一个任务执行(如 script 初始化、setTimeout 回调、setInterval 回调、I/O、UI 渲染触发点);而每次宏任务执行完后,会**清空当前所有微任务队列**(如 Promise.then/catch/finally、MutationObserver 回调、queueMicrotask)。
- 微任务总在当前宏任务结束、下一个宏任务开始前执行,且不中断、不插队
- 一个 Promise 链产生的多个 then,会全部加入本轮微任务队列,依次执行
- await 后面的表达式执行完后,其后续代码会被包装成微任务(等价于 Promise.then)
渲染(paint)发生在宏任务之间
浏览器不是每轮事件循环都渲染。标准流程是:执行一个宏任务 → 执行完所有微任务 → 检查是否需要更新 DOM → 浏览器决定是否进行样式计算、布局、绘制(render)→ 进入下一轮宏任务。
- 如果某个宏任务耗时太久(比如长循环),渲染就会被阻塞,页面卡顿
- requestAnimationFrame (rAF) 是一种特殊的宏任务,它会在**下一次浏览器重绘前**被执行,且每帧最多一次,常用于动画
- rAF 回调的优先级高于 setTimeout,但低于微任务;它不属于微任务,也不在普通宏任务队列尾部,而是由浏览器在渲染前统一调度
事件循环与用户交互、资源加载的协同
用户点击、键盘输入、网络响应(fetch、XMLHttpRequest)、DOMContentLoaded、load 等,都会作为宏任务插入队列(具体时机取决于浏览器实现和资源状态)。
- 点击事件回调是宏任务,所以其中的 Promise.then 会在点击处理完后立即执行(微任务)
- fetch().then() 的回调是微任务,但 fetch 本身发起是同步的,实际响应到达后才把 then 加入微任务队列
- DOM 解析完成 触发 DOMContentLoaded 是宏任务;所有资源(图片、CSS、脚本)加载完 才触发 load 事件,也是宏任务
一个典型执行顺序示例
下面代码的输出顺序能清晰体现机制:
console.log(1); setTimeout(() => console.log(2), 0); Promise.resolve().then(() => console.log(3)); console.log(4);
输出为:1 → 4 → 3 → 2。因为:同步代码(1、4)先执行;Promise.then 是微任务,排在当前宏任务末尾;setTimeout 是宏任务,要等下一轮事件循环。











