事件循环
事件循环(Event Loop)是JavaScript实现异步编程的核心机制.由于JavaScript是单线程的语言,事件循环机制使得他能够非阻塞的处理各种异步操作,如用户交互/网络请求/定时器等.
事件循环的基本流程
- 执行同步代码 : 所有同步任务在主线程上顺序执行
- 处理微任务 : 执行当前微任务队列中的所有任务
- 渲染页面 : 执行UI渲染
- 处理宏任务 : 从宏任务队列中取出一个任务执行
- 循环 : 重复以上过程
宏任务和微任务
宏任务
宏任务代表一些离散的,独立的工作单元.浏览器会在每个宏任务之间进行页面渲染.
常见的宏任务源包括:
setTimeout和setInterval回调- I/O 操作(如文件读取)
- UI渲染
- 用户交互事件(如点击,滚动)
postMessageMessageChannelrequestAnimationFrame(存在争议, 有些浏览器将其视为微任务)
微任务
微任务是更小的任务,在当前宏任务执行完成之后立刻执行,且在页面渲染之前.
常见的微任务源包括:
Promise的then,catch,finally回调MutationObserver回调queueMicrotaskAPIprocess.nextTick(Node.js 环境)
执行顺序示例
console.log('game start')
setTimeout(() => console.log('STO'), 0)
Promise.resolve().then(() => console.log('Promise1')).then(() => console.log('Promise2'))
console.log('end')
输出顺序
game start
end
Promise1
Promise2
STO
详细执行过程分析
- 执行同步代码:
- 输出 'game start'
- 设置一个定时器(在 0 ms后回调加入宏任务队列)
- 创建一个已解决的Promise, 将其then 加入微任务队列
- 输出 'end'
- 同步代码执行完毕,检查微任务队列
- 执行第一个then回调, 输出Promise1
- 该回调返回undefined, 创建一个新的已解决的Promise, 将第二个then加入微任务队列
- 执行第二个then回调, 输出Promise2
- 微任务队列为空,检查是否需要渲染
- 从宏任务队列中取出setTimeout回调并执行, 输出 STO
事件循环和渲染的关系
浏览器通常会在每个宏任务之间进行页面渲染,但在执行微任务队列时不会渲染. 这意味着:
- 微任务会在当前宏任务结束后立即执行, 且会阻塞渲染
- 大量微任务可能会导致页面 '卡死', 因为没有机会渲染
// 这个例子会阻塞页面
function loop(){
Promise.resolve().then(loop)
}
loop()
Node.js与浏览器的事件循环差异
Node.js的事件循环与浏览器有一些不同:
- Node.js有多个阶段(timers, pending callbacks, idle / prepare, poll, check, close callbacks)
process.nextTick比微任务的优先级更高setImmediate是 Node特有的, 在当前事件循环结束时执行
实际使用建议
- 长时间运行的任务: 分解为多个小任务或用
setTimeout/setImmediate让出控制权 - 优先级控制: 使用微任务处理高优先级回调
- 避免微任务爆炸: 不要在一个微任务中创建太多新的微任务