当前位置: 首页 > news >正文

DOM 性能与渲染

系列文章目录

《JavaScript 基础与进阶笔记》(前期偏基础巩固与常见面试点,后续进入闭包、异步、工程化等进阶主题)

  • 第 01 篇:数据类型与类型判断
  • 第 02 篇:变量声明与作用域
  • 第 03 篇:闭包与高阶函数
  • 第 04 篇:函数工厂
  • 第 05 篇:this 指向与绑定
  • 第 06 篇:原型与原型链
  • 第 07 篇:类与继承
  • 第 08 篇:JS 执行机制与异步队列
  • 第 09 篇:数组常用方法
  • 第 10 篇:字符串算法
  • 第 11 篇:常见手写题合集(上)
  • 第 12 篇:常见手写题合集(下)
  • 第 13 篇:Promise 与 async/await
  • 第 14 篇:数据结构基础
  • 第 15 篇:垃圾回收与内存
  • 第 16 篇:DOM 基础全面解析
  • 第 17 篇:DOM 性能与渲染(本文)

文章目录

  • 系列文章目录
  • 前言
  • 一、浏览器渲染主流程
    • 1.1 各阶段简述
    • 1.2 DOM 树 vs 渲染树
  • 二、Reflow、Repaint、Composite
    • 2.1 定义与关系
    • 2.2 常见触发(口述用)
    • 2.3 `display: none` vs `visibility: hidden`
  • 三、渲染队列与强制同步布局
    • 3.1 反模式:读写交替
    • 3.2 优化:批量写、批量读
  • 四、DOM 批量操作与 DocumentFragment
  • 五、CSS / JS 与首屏:阻塞与 FOUC
    • 5.1 CSS
    • 5.2 JavaScript
    • 5.3 与性能指标(了解)
  • 六、动画:`transform` / `opacity` 与 `will-change`
    • 6.1 为何 `transform` / `opacity` 更友好
    • 6.2 `will-change`(慎用)
    • 6.3 `requestAnimationFrame`(预告)
  • 七、优化清单(速查)
  • 八、易混淆点归纳
  • 九、思考与练习
  • 总结

前言

第 16 篇讲了 DOM 节点与 API;本篇回答「改 DOM 之后浏览器在干什么」以及「怎样少卡顿」。核心线索:渲染流水线(DOM → CSSOM → 渲染树 → Layout → Paint → Composite)、回流/重绘/合成的开销差异、强制同步布局的陷阱,以及transform/opacity动画DocumentFragment批量操作等优化手段。CSS/JS 阻塞与 FOUC 与第 16 篇脚本部分衔接,此处从渲染视角补全。


一、浏览器渲染主流程

从输入 URL 到像素上屏,与前端性能最相关的一段可简化为:

HTML 解析 → DOM 树 ↘ CSS 解析 → CSSOM 树 → 渲染树(Render Tree)→ Layout(布局/回流) → Paint(绘制/重绘) → Composite(合成)

1.1 各阶段简述

阶段产出说明
DOMDOM 树HTML 解析;第 16 篇
CSSOMCSS 对象模型外部/内联 CSS 解析
Render Tree渲染树DOM + 样式;不含display: none等不可见节点
Layout几何信息计算位置与尺寸;也称Reflow(回流)
Paint绘制记录填充颜色、文字、阴影等;Repaint(重绘)
Composite图层合成GPU 合并层,输出屏幕

注意:现代浏览器会对步骤合并与跳过(如仅改transform可能跳过 Layout/Paint),下节按概念模型理解即可。

1.2 DOM 树 vs 渲染树

DOM 树渲染树
display: none不在
visibility: hidden(占空间,不可见)
<head>内 meta 等通常不在(无盒)

二、Reflow、Repaint、Composite

中文常称回流(Reflow)Layout重绘(Repaint)Paint

2.1 定义与关系

  1. 回流(Reflow / Layout)— 几何属性(宽高、位置、display 等)变化,需重新计算布局;开销最大
  2. 重绘(Repaint)— 外观变化(颜色、背景、visibility等)不影响布局,只重画像素;开销次之。
  3. 合成(Composite)— 已有图层上变换(如transformopacity),常由GPU完成;开销相对最小

关系:回流一般会触发重绘;重绘不一定回流;合成通常不触发布局(在独立合成层上时)。

2.2 常见触发(口述用)

易触发回流

  • 增删 DOM、改变盒模型(width/height/margin/padding
  • 读写offsetWidthclientHeightgetBoundingClientRect()等(见下节)
  • 窗口 resize、字体加载改变度量
  • display: none↔ 其他 display

易触发重绘(不一定回流)

  • colorbackgroundbox-shadow
  • visibility: hidden(保留布局)

倾向只走合成

  • transformtranslatescalerotate
  • opacity(在 promoted 层上时)
constel=document.querySelector(".box");// 易触发 Layout + Paintel.style.width="200px";el.style.left="100px";// 动画更友好:Compositeel.style.transition="transform 0.3s";el.style.transform="translateX(100px)";

2.3display: nonevsvisibility: hidden

属性渲染树占布局空间典型触发
display: none移除回流
visibility: hidden保留重绘

三、渲染队列与强制同步布局

浏览器会把多次样式变更批量渲染队列,在合适时机(如帧末)统一 Layout/Paint,避免每改一行 CSS 就全页算一遍。

读取以下属性会强制刷新队列(必须先算出最新布局),称为强制同步布局(Forced Synchronous Layout / Layout Thrashing)

  • offsetWidth/offsetHeight/offsetTop/offsetLeft
  • clientWidth/clientHeight
  • scrollWidth/scrollHeight/scrollTop
  • getComputedStyle(...)
  • getBoundingClientRect()

3.1 反模式:读写交替

constel=document.querySelector(".box");// ❌ 每次循环:写 style → 读 offsetWidth → 强制回流for(leti=0;i<100;i++){el.style.width=`${i}px`;console.log(el.offsetWidth);}

3.2 优化:批量写、批量读

// ✅ 先批量写constpositions=Array.from({length:100},(_,i)=>i);positions.forEach((i)=>{el.style.width=`${i}px`;});// 再读一次console.log(el.offsetWidth);

原则先写后读读写分离;无法分离时用requestAnimationFrame把读放到下一帧(第 20 篇详述 rAF)。


四、DOM 批量操作与 DocumentFragment

多次appendChild到同一父节点,可能触发多次回流。先用DocumentFragment在内存中组好子树,一次插入:

constul=document.querySelector("ul");constfrag=document.createDocumentFragment();for(leti=0;i<500;i++){constli=document.createElement("li");li.textContent=`item${i}`;frag.appendChild(li);}ul.appendChild(frag);// 一次插入,减少 Layout 次数

其他手段

  • display: none或离屏容器上改完再挂回(旧技巧,慎用可访问性)
  • 虚拟列表(第 12 篇):控制 DOM 节点总数
  • 框架的批量更新(Virtual DOM diff 后一次 patch)

五、CSS / JS 与首屏:阻塞与 FOUC

5.1 CSS

  • 不阻塞DOM 解析(HTML 继续建 DOM)。
  • 阻塞渲染:浏览器倾向在 CSSOM 就绪前不绘制,避免FOUC(Flash of Unstyled Content,无样式内容闪烁)
  • 实践:关键 CSS 放<head>尽早加载;非关键 CSS 可异步或按需。

5.2 JavaScript

  • 默认<script>阻塞 HTML 解析(第 16 篇)。
  • defer:并行下载,DOM 解析完再按序执行。
  • async:下载完即执行,不保证顺序。

5.3 与性能指标(了解)

  • FCP:首次有内容绘制。
  • LCP:最大内容块绘制(Core Web Vitals 之一)。
  • 阻塞渲染的资源越晚、越少,首屏通常越好(还需结合体积、CDN、缓存等)。

六、动画:transform/opacitywill-change

6.1 为何transform/opacity更友好

  • 改变top/left/width常触发Layout
  • transformopacity可在合成层上由 GPU 处理,跳过主线程 Layout(在层已提升且仅合成属性变化时)。
constcard=document.querySelector(".card");card.style.transition="transform 0.3s ease, opacity 0.3s";card.style.transform="translateY(-8px)";card.style.opacity="0.95";

移动元素优先translate,而非top/left

6.2will-change(慎用)

.card{will-change:transform;}
  • 作用:提前告知浏览器某属性将变,可能创建独立合成层
  • 风险:层过多占GPU 内存;长期开启反而浪费。
  • 建议:动画开始前加、结束后移除;不要对大量元素默认will-change: transform

6.3requestAnimationFrame(预告)

动画循环应用rAF对齐刷新率,而非setTimeout(16);滚动节流、后台标签暂停等见第 20 篇。


七、优化清单(速查)

问题方向
频繁回流合并 DOM 写操作、DocumentFragment、虚拟列表
Layout Thrashing读写分离,避免循环内读 layout
动画卡顿transform/opacity,少改 layout 属性
首屏 FOUCCSS 提前、关键 CSS 内联或 preload
解析阻塞scriptdefer/async
层爆炸will-change按需、用完即删

八、易混淆点归纳

  1. 回流 ⊃ 重绘(几何变必画);重绘不一定回流
  2. Composite 不是零成本,但通常比全页 Layout 轻。
  3. offset*会强制布局,与是否刚写过 style 有关。
  4. CSS 阻塞渲染、不阻塞 DOMJS 默认阻塞解析
  5. visibility: hidden回流?— 一般重绘为主;display: none回流
  6. rAF 不是微任务也不是宏任务,在渲染前回调(第 08、20 篇对照)。

九、思考与练习

1.只改background-color,会回流吗?

解析:通常不触发布局,主要重绘

2.循环 100 次el.style.left = i + 'px'且每次读offsetWidth,为何慢?

解析:每次读 layout 属性强制同步布局,导致多次回流

3.DocumentFragment插入后,fragment 里的节点去哪了?

解析:插入时子节点移到真实父节点,fragment变空,可复用或丢弃。

4.为何移动端大量will-change: transform可能更卡?

解析:合成层过多占 GPU 内存,合成本身也有开销。

5.defer脚本与DOMContentLoaded谁先?

解析:defer 脚本按序执行完后,再触发DOMContentLoaded(无其他阻塞时)。


总结

  • 流水线:DOM + CSSOM →渲染树Layout(回流)Paint(重绘)Composite(合成)
  • 开销:回流 > 重绘 > 合成(概念上);避免读写交替造成强制同步布局。
  • 优化DocumentFragment批量 DOM;动画用transform/opacitywill-change慎用
  • 加载:CSS 放 head 减 FOUC;JS 用defer/async减阻塞(见第 16 篇)。

下一篇讲事件系统:捕获/冒泡、targetvscurrentTarget、委托与passive等。

http://www.cnnetsun.cn/news/2529838.html

相关文章:

  • UE5库存系统设计:FStruct+GameplayTags数据驱动方案
  • 零基础30天掌握渗透测试实战路径
  • kswapd0异常飙升?Linux内核级挖矿攻击深度排查与清除
  • 【MySQL全面教学】MySQL基础SQL语句Day3(2026年)
  • Hurley开源工具:C#到C语言的语义级跨平台翻译
  • JustTrustMe与Frida协同构建Android可信动态分析基座
  • 大模型MoE架构揭秘:为何仅2%参数决定推理性能
  • 企业团队如何利用Taotoken统一管理多项目API密钥与用量
  • DownKyi终极指南:5个技巧让你成为B站视频下载专家
  • Unity Shader从GPU原理入门:顶点与片元着色器硬核解析
  • 观察在流量高峰时段通过Taotoken调用不同模型的响应时间表现
  • Win11Debloat:三步让你的Windows 11告别卡顿,重获新生
  • 【YOLO目标检测全栈实战】69 内存碎片化:量化模型在边缘设备上的隐形杀手
  • Unity手搓合并网格工具:从Draw Call优化到生产级鲁棒性
  • 企业级定制化条形码解析:突破ZXing框架限制的高性能解决方案
  • 3步搞定Spotify音乐永久保存:开源下载神器完全指南
  • CTF自动化实战指南:Web与逆向脚本设计+e春秋靶场API深度利用
  • Unity 2D基础:2D相机Orthographic的参数调节
  • Source Han Serif CN:终极免费字体解决方案快速上手指南
  • 企业AI使用政策设计:DeepSeek类大模型的合规落地七步法
  • ZXing条形码识别库的模块化架构演进与性能优化策略
  • Lovable ML平台搭建避坑清单(2020–2024年137个真实故障案例提炼的12个致命陷阱)
  • 在构建自动化工作流时集成稳定可靠的大模型API
  • 【AI Agent机器学习实战指南】:20年专家亲授5大落地陷阱与3步高效部署法
  • AI Agent赋能5G核心网自动化闭环(独家实测数据:OSS响应效率提升87%)
  • 从串口数据到实时波形:SerialPlot终极可视化指南
  • 从立案到执行全链路AI协同(某红圈所内部培训PPT首度流出:含12个不可商用的训练数据陷阱)
  • gibMacOS深度技术解析:跨平台macOS组件下载与构建系统
  • 攻克葫芦科转化难题:甜瓜高效遗传转化体系构建与服务实践
  • 别再硬扛了!书匠策AI把毕业论文拆成了“填空题“,2025届必看科普