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

相关推荐
xiaotao1313 小时前
第九章:Vite API 参考手册
前端·vite·前端打包
午安~婉3 小时前
Electron桌面应用聊天(续)
前端·javascript·electron
彧翎Pro3 小时前
基于 RO1 noetic 配置 robosense Helios 32(速腾) & xsense mti 300
前端·jvm
小码哥_常3 小时前
解锁系统设置新姿势:Activity嵌入全解析
前端
之歆4 小时前
前端存储方案对比:Cookie-Session-LocalStorage-IndexedDB
前端
哟哟耶耶4 小时前
vue3-单文件组件css功能(:deep,:slotted,:global,useCssModule,v-bind)
前端·javascript·css
是罐装可乐4 小时前
深入理解“句柄(Handle)“:从浏览器安全到文件系统访问
前端·javascript·安全
华科易迅4 小时前
Vue如何集成封装Axios
前端·javascript·vue.js
康一夏4 小时前
Next.js 13变化有多大?
前端·react·nextjs
糖炒栗子03264 小时前
前端项目标准环境搭建与启动
前端