6.性能优化实战:减少回流与重绘 来源:https://w0hog67yl81.feishu.cn/wiki/IRM4wK4Jgieak3kNFEqc2cuCnFb 采集状态:已完成正文采集;每次约0.55屏连续滚动到文末,按block id和代码行号累积;代码行号连续。 一、 理解回流 (Reflow) 与重绘 (Repaint) 为了优化,我们首先要精确理解这两个概念: • 回流 (Reflow): 当元素的尺寸、结构或位置发生改变时,浏览器需要重新计算元素的几何属性(即重新进行“布局”阶段),这个过程称为“回流”。回流会影响其周围甚至整个页面的其他节点,是开销非常大的一种操作。回流必然导致重绘。 • 重绘 (Repaint): 当元素的视觉样式(如color, background-color)发生改变,但其几何属性没有变化时,浏览器会跳过布局阶段,直接为该元素重新绘制(即重新进行“绘制”阶段),这个过程称为“重绘”。相比回流,重绘的开销较小。 优化的核心思想:避免不必要的回流,将多次回流合并为一次。 二、 常见触发回流与重绘的操作 了解触发时机是避免它们的第一步。 常见触发回流 (Reflow) 的操作: 1. 页面首次渲染:这是不可避免的回流。 2. DOM节点操作:增加、删除或修改DOM节点。 3. 元素几何属性变更:修改width, height, padding, margin, border等。 4. 元素位置变更:position, top, left等。 5. 字体变更:修改font-size, font-family等。 6. 窗口尺寸变更:resize事件触发。 7. 获取特定的布局属性:这是最容易被忽略的一点!当你用JavaScript读取以下属性时,浏览器为了返回精确的值,会强制立即执行一次回流: ◦ offsetTop, offsetLeft, offsetWidth, offsetHeight ◦ scrollTop, scrollLeft, scrollWidth, scrollHeight ◦ clientTop, clientLeft, clientWidth, clientHeight ◦ getComputedStyle() 仅触发重绘 (Repaint) 的操作: • 修改color, background-color, visibility, outline, box-shadow等不影响布局的样式。 三、 优化实战:具体的编码技巧 1. 批量修改DOM 问题:当需要向DOM中添加多个元素时,如果直接在循环中逐个appendChild,每次操作都会触发一次回流。 // 反例:会触发多次回流 const container = document.getElementById('container'); for (let i = 0; i < 100; i++) { const el = document.createElement('p'); el.textContent = i; container.appendChild(el); // 每次循环都可能触发回流 } 优化技巧: • 使用 DocumentFragment: DocumentFragment是一个存在于内存中的轻量级DOM容器,对它的所有操作都不会触发回流。我们可以先把所有新节点添加到Fragment中,最后再将Fragment一次性添加到真实DOM中。 // 优例:只触发一次回流 const container = document.getElementById('container'); const fragment = document.createDocumentFragment(); // 创建文档片段 for (let i = 0; i < 100; i++) { const el = document.createElement('p'); el.textContent = i; fragment.appendChild(el); // 在内存中操作,不触发回流 } container.appendChild(fragment); // 一次性插入,触发单次回流 • 隐藏元素后操作:将需要多次操作的元素先display: none,完成所有操作后,再恢复显示。这会产生两次回流(隐藏和显示),但远优于N次。 const container = document.getElementById('container'); container.style.display = 'none'; // 回流 1 // ... 在此进行多次DOM操作 ... container.style.display = 'block'; // 回流 2 2. 批量修改样式与读写分离 问题:在循环中交替读取元素的布局属性和设置样式,会导致“布局抖动”(Layout Thrashing)。浏览器被迫在每次循环中都强制回流。 // 反例:布局抖动 const elements = document.querySelectorAll('.box'); for (let i = 0; i < elements.length; i++) { // 读取 offsetWidth (读操作),强制浏览器回流 const width = elements[i].offsetWidth; // 设置 style.width (写操作),再次导致布局变更 elements[i].style.width = width + 10 + 'px'; } 优化技巧:读写分离 (Read/Write Separation) 将读操作和写操作完全分开,先批量读取所有需要的值并缓存,然后批量进行样式更新。 // 优例:读写分离 const elements = document.querySelectorAll('.box'); const widths = []; // 读操作 for (let i = 0; i < elements.length; i++) { widths.push(elements[i].offsetWidth); } // 写操作 for (let i = 0; i < elements.length; i++) { elements[i].style.width = widths[i] + 10 + 'px'; } 优化技巧:使用 classList 对于复杂的样式变更,推荐将样式组合定义在一个CSS类中,然后通过JavaScript切换class。浏览器可以更高效地处理class的变更。 /* style.css */ .large-box { width: 200px; height: 200px; border: 2px solid blue; } // JS const box = document.getElementById('my-box'); // 替代 style.width, style.height 等多次修改 box.classList.add('large-box'); // 一次操作,浏览器会优化渲染 通过以上方法,我们可以有效减少渲染引擎的工作量,让网页动画和交互变得更加流畅。