浏览器的事件循环是负责执行代码、收集和处理事件以及执行队列中子任务的核心机制,其运行流程基于执行上下文栈、任务队列(宏任务与微任务)的协作,具体如下:
执行上下文栈
JavaScript代码执行时,会进入不同的执行环境(如全局环境、函数环境),每个环境对应一个执行上下文。全局上下文首先被创建并入栈,调用函数时,函数上下文入栈;栈顶上下文执行完毕后出栈。栈的结构决定了代码的执行顺序,遵循“后进先出”原则。
任务队列的分类与优先级
任务队列分为宏任务队列和微任务队列,两类任务在事件循环中的处理顺序不同:
- 宏任务:包括setTimeout、setInterval、I/O操作、UI渲染等。这类任务通常由浏览器外部事件触发,优先级较低。
- 微任务:包括Promise、MutationObserver(已废弃的Object.observe不纳入讨论)。微任务由JavaScript内部逻辑触发,优先级高于宏任务。
事件循环的核心流程
- 主线程执行同步代码:主线程从全局上下文开始,逐行执行JavaScript代码,形成执行上下文栈。遇到异步任务(如setTimeout、Promise)时,将其挂起,不立即执行。
- 异步任务分发至任务队列:当异步任务完成(如定时器到期、Promise状态变更),浏览器将其放入对应的任务队列:微任务进入微任务队列,宏任务进入宏任务队列。
- 清空微任务队列:执行上下文栈清空(仅剩全局上下文)后,事件循环优先处理微任务队列。所有微任务按“先进先出”顺序执行,直到队列为空。
- 处理宏任务队列:微任务队列清空后,事件循环从宏任务队列中取出优先级最高的任务(如setTimeout),按“先进先出”顺序执行一个宏任务。
- 循环交替执行:重复步骤3-4,交替清空微任务队列和宏任务队列,直至所有队列为空,最后全局上下文出栈,程序结束。
关键注意事项
- 微任务优先级:微任务会在当前宏任务结束后、下一个宏任务开始前全部执行完毕,因此可能阻塞渲染(如大量微任务会导致UI更新延迟)。
- Node.js与浏览器的差异:Node.js的事件循环基于libuv实现,与浏览器机制不同(如setImmediate为Node特有宏任务),需区分应用场景。