一文搞清楚浏览器js事件循环和渲染事件

在浏览器环境中,JavaScript 事件循环(Event Loop)渲染事件 的执行过程是 Web 性能优化和流畅交互的重要基础。以下是详细的执行流程,包括 同步任务、微任务、宏任务、渲染机制 等所有可能的节点。


📌 1. 事件循环(Event Loop)整体执行流程

浏览器的 JavaScript 运行环境基于 事件循环(Event Loop) ,其执行逻辑如下:

  1. 执行同步代码(Script)

    • 代码最先进入 调用栈(Call Stack) ,按照顺序执行 同步任务(Synchronous Tasks)。
  2. 执行微任务(Microtasks)

    • 运行所有 微任务(Microtasks) ,包括:

      • Promise.then/catch/finally
      • MutationObserver
      • queueMicrotask()
  3. 执行宏任务(Macrotasks)

    • 运行 一个 宏任务(Macrotask),包括:

      • setTimeout
      • setInterval
      • setImmediate(仅 Node.js)
      • requestAnimationFrame
      • I/O 操作
      • UI 渲染
      • MessageChannel
  4. 渲染更新(仅浏览器)

    • 如果 JavaScript 执行结束,浏览器会检查是否需要更新 UI,并执行 下一帧的渲染(Frame Rendering)
  5. 重复以上过程

    • 事件循环(Event Loop)继续重复上述流程。

📌 2. 浏览器的执行步骤细节

浏览器的事件循环不仅仅处理 JavaScript 代码,还涉及 渲染、UI 更新,主要执行顺序如下:

🔹 1. 解析 HTML & 构建 DOM

  • 浏览器解析 HTML ,将其转换为 DOM 树(DOM Tree)

🔹 2. 解析 CSS & 构建 CSSOM

  • CSS 解析器 解析 linkstyle 内嵌 CSS,构建 CSSOM(CSS 对象模型)

🔹 3. 运行 JavaScript

  • 执行 JavaScript 代码

    • 运行 同步代码(同步任务) ,如变量赋值、函数调用等。
    • 当遇到 异步任务 (如 setTimeoutPromise),它们会被放入相应的 任务队列(Task Queue) ,等待执行。

🔹 4. 处理微任务(Microtasks)

  • 微任务优先级高于宏任务 ,当同步代码执行完成后,立即执行所有 微任务

    • 例如 Promise.then()MutationObserverqueueMicrotask()

🔹 5. 处理宏任务(Macrotasks)

  • 事件循环每次从宏任务队列取出 一个 任务执行,包括:

    • setTimeout
    • setInterval
    • requestAnimationFrame
    • I/O 任务
    • MessageChannel

🔹 6. 计算布局(Recalculate Style & Layout)

  • 如果 DOM 或 CSS 发生变化 ,浏览器会 重新计算元素的样式和布局

    • Recalculate Style(重新计算样式)
    • Layout(布局计算,确定元素的位置和大小)

🔹 7. 处理合成(Paint & Composite)

  • Paint(绘制) :将像素绘制到屏幕上。
  • Composite(合成) :合成多个图层,显示最终画面。

📌 3. 详细的事件执行顺序(实例解析)

我们来看一个具体的案例:

javascript 复制代码
console.log("同步代码开始");  // 1️⃣

setTimeout(() => {
  console.log("setTimeout 宏任务");  // 6️⃣
}, 0);

Promise.resolve().then(() => {
  console.log("Promise 微任务");  // 3️⃣
});

console.log("同步代码结束");  // 2️⃣

📝 执行顺序解析

步骤 任务类型 代码 说明
1️⃣ 同步任务 console.log("同步代码开始") 直接执行
2️⃣ 同步任务 console.log("同步代码结束") 直接执行
3️⃣ 微任务 Promise.then() 微任务放入微任务队列,立即执行
4️⃣ 检查是否有剩余微任务 微任务队列为空,继续
5️⃣ 宏任务 setTimeout 执行定时器回调
6️⃣ 日志输出 "setTimeout 宏任务"

最终 控制台输出顺序

javascript 复制代码
同步代码开始
同步代码结束
Promise 微任务
setTimeout 宏任务

📌 4. requestAnimationFrame(帧渲染机制)

requestAnimationFrame() 是专门用于优化动画的 宏任务,它的特点是:

  1. 在浏览器下一帧渲染前执行(通常 16.67ms 一次)。
  2. setTimeout(fn, 16) 更精准,且不会阻塞 UI 渲染

示例:

scss 复制代码
function renderFrame() {
  console.log("下一帧渲染");
  requestAnimationFrame(renderFrame);
}

requestAnimationFrame(renderFrame);

执行时机

  • 浏览器刷新时触发(即下一帧到来前)。
  • 如果页面切换到后台,动画会暂停,节省 CPU 资源

📌 5. setTimeout vs requestAnimationFrame

机制 适用场景 运行频率 是否阻塞渲染
setTimeout(fn, 16) 粗略的定时器 约 16ms 一次 可能阻塞渲染
requestAnimationFrame(fn) 平滑动画 下一帧 渲染前 不阻塞,推荐

🚀 建议: 使用 requestAnimationFrame() 来实现动画,而不是 setTimeout(fn, 16),能提供 更流畅的动画效果


📌 6. 总结

🔹 事件循环的完整顺序

  1. 执行 同步任务(script)。
  2. 执行 所有微任务(Promise、MutationObserver)。
  3. 渲染(如果需要)。
  4. 执行 一个 宏任务(setTimeout, setInterval)。
  5. 重复循环

🔹 渲染过程

  1. JS 执行完毕
  2. 计算样式(Recalculate Style)
  3. 计算布局(Layout)
  4. 绘制(Paint)
  5. 合成(Composite)
  6. 显示画面

📢 优化建议

  • 使用 requestAnimationFrame() 优化动画
  • 减少 DOM 操作,避免 Repaint & Reflow
  • 合理使用 setTimeout,避免阻塞渲染

这样,你就完整掌握了浏览器 JS 事件循环渲染机制 的详细流程了!🚀

相关推荐
LFly_ice15 小时前
学习React-23-React-router
前端·学习·react.js
我叫张小白。15 小时前
TypeScript对象类型与接口:构建复杂数据结构
前端·javascript·typescript
墨客希15 小时前
如何快速掌握大型Vue项目
前端·javascript·vue.js
大福ya15 小时前
AI开源项目改造NextChat(ChatGPT-Next-Web)实现前端SSR改造打造一个初始框架
前端·chatgpt·前端框架·开源·aigc·reactjs·ai编程
n***333515 小时前
SpringBoot返回文件让前端下载的几种方式
前端·spring boot·后端
纯粹的热爱16 小时前
🌐 阿里云 Linux 服务器 Let's Encrypt 免费 SSL 证书完整部署指南
前端
北辰alk16 小时前
Vue3 自定义指令深度解析:从基础到高级应用的完整指南
前端·vue.js
小熊哥72216 小时前
谈谈最进学习(低延迟)直播项目的坎坷与收获
前端
用户892254118290116 小时前
游戏框架文档
前端
Holin_浩霖16 小时前
mini-react 实现function 组件
前端