1. 浏览器渲染流程概览
浏览器渲染页面主要分为以下阶段:
- 解析HTML/CSS → 构建DOM树和CSSOM树
- 合并渲染树(Render Tree) → 结合DOM和CSSOM
- 布局(Layout/Reflow) → 计算元素的位置和尺寸
- 绘制(Paint) → 填充像素(如颜色、边框等)
- 合成(Composite) → 将图层合并到屏幕上
2. JS执行与渲染的互斥性
- 单线程机制:JS引擎和渲染引擎共享同一主线程,无法并行执行。
- 阻塞逻辑:JS执行会暂停渲染,直到当前JS任务完成。
- 例:一段耗时JS代码运行时,页面会“冻结”,无法更新渲染。
3. 事件循环(Event Loop)中的渲染时机
浏览器通过事件循环协调任务,流程如下:
- 执行当前宏任务(如JS脚本、事件回调)
- 执行所有微任务(如Promise回调)
- 检查是否需要渲染
- 若距离上次渲染超过16.6ms(≈60Hz屏幕刷新率),则执行渲染流程。
- 若无需渲染,直接进入下一轮事件循环。
4. JS如何触发渲染?
-
直接触发:JS修改DOM或样式后,可能触发以下流程:
- 样式计算(Recalculate Style)
- 布局(Layout) → 若修改尺寸、位置等几何属性
- 绘制(Paint) → 若修改颜色、背景等外观属性
- 合成(Composite) → 若仅修改透明度、变换等(跳过布局和绘制)
-
优化策略:
- 浏览器会合并多次DOM操作,减少不必要的重排/重绘。
- 使用
requestAnimationFrame
将JS动画与渲染帧同步,提升性能。
5. 关键结论
- JS执行优先于渲染:主线程必须先完成当前JS任务,才能处理渲染。
- 渲染并非紧随JS结束:浏览器根据屏幕刷新率决定是否渲染,通常每秒60次(16.6ms/帧)。
- 长任务导致卡顿:若JS执行超过50ms,用户会感知延迟(如点击无响应)。
6. 验证示例
// 案例1:同步JS阻塞渲染
document.body.style.background = "red"; // 修改样式
alert("阻塞渲染"); // 同步弹窗暂停JS执行
// 此时页面背景不会变红,需关闭弹窗后才会渲染
// 案例2:异步JS允许渲染穿插
requestAnimationFrame(() => {
document.body.style.background = "blue"; // 在下一帧渲染前执行
});
7. 性能优化建议
- 避免长任务:将复杂计算拆分(如用
setTimeout
或Web Worker
)。 - 减少强制同步布局:避免在JS中连续读取布局属性(如
offsetHeight
),导致多次重排。 - 使用CSS动画替代JS动画:利用合成阶段的优化(如
transform
、opacity
)。
通过理解JS与渲染的协作机制,可显著提升页面性能与用户体验。