浏览器事件循环

浏览器的事件循环(Event Loop)是 JavaScript 实现异步编程的核心机制,它负责协调同步任务、异步任务的执行顺序,确保单线程的 JavaScript 能够高效处理并发操作(如网络请求、定时器、用户交互等)。

一、核心背景:JavaScript 的单线程特性

JavaScript 设计为单线程(同一时间只能执行一个任务),这是因为它最初用于处理浏览器 DOM 操作 ------ 多线程可能导致 DOM 冲突(如同时修改同一个元素)。

但单线程会带来问题:如果有一个耗时任务(如网络请求),会阻塞后续代码执行,导致页面卡顿。因此,浏览器引入了事件循环机制,配合异步 API,实现 "非阻塞" 的并发效果。

二、事件循环的基本流程

事件循环的核心是协调调用栈(Call Stack)任务队列(Task Queue)微任务队列(Microtask Queue)浏览器内核模块的工作流程,步骤如下:

  1. 执行同步代码:所有同步任务直接进入调用栈,按 "先进后出" 顺序执行,执行完后出栈。

  2. 处理异步任务 :遇到异步任务(如setTimeoutfetchDOM事件),JavaScript 引擎不会等待其完成,而是将其交给浏览器的内核模块(如定时器模块、网络模块、DOM 模块)处理,继续执行后续同步代码。

  3. 异步任务完成后入队 :当异步任务完成(如定时器到期、网络请求返回、用户点击),内核模块会将对应的回调函数 放入任务队列 (宏任务队列)或微任务队列

  4. 执行队列中的任务:当调用栈为空时(同步代码执行完毕),事件循环开始工作:

    • 先清空微任务队列:将所有微任务按顺序放入调用栈执行,直到微任务队列为空。
    • 再执行一个宏任务:从宏任务队列取第一个任务放入调用栈执行。
    • 重复以上步骤,形成循环。

三、宏任务(Macrotask)与微任务(Microtask)

异步任务分为两类,优先级不同:

1. 宏任务(Macrotask)

  • 类型setTimeoutsetIntervalsetImmediate(Node 特有)、I/O操作(如网络请求、文件读写)、DOM事件(如 click、load)、script标签中的整体代码
  • 特点 :每次事件循环只执行一个宏任务,执行完后会触发页面渲染(如果需要),再处理微任务。

2. 微任务(Microtask)

  • 类型Promise.then/catch/finallyasync/await(本质是 Promise 语法糖)、queueMicrotask()MutationObserver(监听 DOM 变化)。
  • 特点 :优先级高于宏任务,一个宏任务执行完后,会清空所有微任务再执行下一个宏任务。

过去把消息队列简单分为宏队列和微队列,这种说法目前已经无法满足复杂的浏览器环境,取而代之的是一种更加灵活多变的处理方式

根据W3C官方的解释,每个任务有不同的类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪一个队列的任务。但浏览器必须有一个微队列,微队列的任务一定具有最高的优先级,必须优先调度执行。

四、经典示例:理解执行顺序

js 复制代码
console.log('1'); // 同步任务

setTimeout(() => {
  console.log('2'); // 宏任务
}, 0);

Promise.resolve().then(() => {
  console.log('3'); // 微任务
}).then(() => {
  console.log('4'); // 微任务
});

console.log('5'); // 同步任务

执行结果1 → 5 → 3 → 4 → 2解析

  1. 同步代码console.log('1')console.log('5')先执行,调用栈清空。
  2. 处理微任务队列:执行两个then回调,输出34,微任务队列为空。
  3. 执行宏任务队列:setTimeout回调,输出2
js 复制代码
function a(){
    console.log(1)
    Promise.resolve().then(function(){
        console.log(2)
    })
}

setTimeout(function(){
    console.log(3)
    Promise.resolve().then(a)
},0)

Promise.resolve().then(function(){
    console.log(4)
})

console.log(5)

//5 4 3 1 2
js 复制代码
setTimeout(() => {
    console.log('1')
},0)

Promise.resolve().then(() => {
    console.log('4')
})

async function asyncFun(){
    console.log('3')
    await Promise.resolve()
    console.log('5')
}

asyncFun()
console.log('2')

// 3 2 4 5 1

五、事件循环与页面渲染的关系

每次宏任务执行完、微任务队列清空后,浏览器会检查是否需要重新渲染页面(如 DOM 更新、样式变化),然后再进入下一次事件循环。

这也是为什么频繁的 DOM 操作建议放在微任务中批量处理 ------ 减少渲染次数,提升性能。

六、总结

事件循环的核心逻辑可简化为:同步代码优先执行 → 调用栈空时,先清微任务 → 再执行一个宏任务 → 重复循环

理解事件循环有助于解决异步代码的执行顺序问题(如回调地狱、Promise 链式调用),也是前端面试的高频考点。记住:微任务优先级高于宏任务,且同一轮循环中微任务会被全部执行

相关推荐
鹏多多4 小时前
React跨组件数据共享useContext详解和案例
前端·javascript·react.js
江城开朗的豌豆6 小时前
React生命周期:从诞生到更新的完整旅程
前端·javascript·react.js
江城开朗的豌豆6 小时前
Redux vs Context+Hooks:前端状态管理的双雄对决
前端·javascript·react.js
正义的大古8 小时前
OpenLayers地图交互 -- 章节四:修改交互详解
前端·javascript·vue.js
深耕AI9 小时前
【9/10】前端认证整合:Vue.js 中处理 JWT,实现登录页面和受保护路由
前端·javascript·vue.js
木觞清13 小时前
补环境-JS原型链检测:在Node.js中完美模拟浏览器原型环境
开发语言·javascript·node.js
知识分享小能手13 小时前
React学习教程,从入门到精通,React Router 语法知识点及使用方法详解(28)
前端·javascript·学习·react.js·前端框架·vue·react
黄毛火烧雪下13 小时前
React中Class 组件 vs Hooks 对照
前端·javascript·react.js
gnip14 小时前
工作常用设计模式
前端·javascript