一文搞清楚浏览器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 事件循环渲染机制 的详细流程了!🚀

相关推荐
晓晨的博客4 分钟前
ROS1录制的bag包转换为ROS2格式
前端·chrome
Wect12 分钟前
LeetCode 72. 编辑距离:动态规划经典题解
前端·算法·typescript
donecoding25 分钟前
别再让 pnpm 跟着 nvm 跑了!独立安装终极指南
前端·node.js·前端工程化
GISer_Jing27 分钟前
AI全栈转型_TS后端学习路线
前端·人工智能·后端·学习
竹林81827 分钟前
被The Graph的GraphQL查询坑了三天,我用一个真实DeFi项目把链上数据索引彻底搞懂了
前端·graphql
漫游的渔夫27 分钟前
前端开发者做 Agent:别只会执行,用 4 类失败策略让 AI 知道怎么停
前端·人工智能·typescript
用户0595401744630 分钟前
把多级缓存一致性验证从手工测试换成 Pytest 参数化,Bug 排查时间缩短 90%
前端·css
暗不需求31 分钟前
深入理解 LangChain:AI 应用开发框架的工程化实践
前端·langchain
用户059540174461 小时前
把 Redis 持久化测试从 800 行 Shell 换成 30 行 pytest,排错效率翻了 10 倍
前端·css
GISer_Jing1 小时前
AI全栈工程师知识体系全景:从前后端核心架构到落地项目全拆解
前端·人工智能·后端·ai编程