前端事件循环(Event Loop)是 JavaScript 运行时处理异步任务的核心机制,它决定了代码执行的顺序和优先级,使 JavaScript 在单线程环境中高效处理异步任务,确保用户界面的流畅性和响应性。
单线程与异步执行的矛盾
JavaScript 在浏览器环境中是单线程运行的,这意味着它同一时间只能执行一个任务。例如,当执行一段复杂的计算任务时,其他任务(如用户点击事件、页面渲染)都必须等待该计算完成。这种特性在处理需要大量时间的任务时,可能会导致页面卡顿,影响用户体验。然而,前端开发中存在许多耗时操作,如网络请求(fetch
)、定时器(setTimeout
、setInterval
)等。如果这些操作都以同步方式执行,整个页面将会冻结,直到操作完成。为了解决这个矛盾,JavaScript 引入了事件循环机制,它允许在执行同步任务的同时,异步处理其他任务。
工作原理
调用栈
同步代码逐行执行(后进先出,LIFO)
函数调用时入栈,执行完毕出栈
任务队列
宏任务队列(MacroTask Queue) :setTimeout
、setInterval
、DOM 事件回调、I/O 操作
微任务队列(MicroTask Queue) :Promise.then
、MutationObserver
、queueMicrotask
渲染管线(Render Pipeline) :样式计算、布局、绘制(requestAnimationFrame 回调在此阶段执行)
事件循环流程
markdown
while (true) {
1. 执行调用栈中的同步代码(直到栈空)
2. 执行所有微任务(清空微任务队列)
3. 渲染页面(如果需要)
4. 取一个宏任务执行
}
事件循环不断检查调用栈是否为空。当调用栈为空时,事件循环从任务队列中取出一个任务(回调函数),将其压入调用栈执行。如此循环往复,实现异步任务的有序执行。
示例
javascript
console.log('1'); // 同步代码
setTimeout(() => {
console.log('2'); // 宏任务
Promise.resolve().then(() => console.log('3')); // 微任务
}, 0);
Promise.resolve().then(() => {
console.log('4'); // 微任务
setTimeout(() => console.log('5'), 0); // 宏任务
});
// 输出顺序:1 → 4 → 2 → 3 → 5
事件循环每次从宏任务队列中取出一个宏任务执行。在执行宏任务过程中,可能会产生新的宏任务或微任务。
在当前宏任务执行完毕后,事件循环会立即处理微任务队列中的所有微任务,直到微任务队列为空,才会去取下一个宏任务。
执行优先级
同步代码 > 微任务 > 宏任务 > 渲染
微任务的嵌套执行
微任务队列必须完全清空后,才会执行下一个宏任务或渲染