关于性能优化需要知道的——重排和重绘

793 阅读4分钟

「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

当浏览器下载完页面所需元素(html标记,css层叠样式表,javascript,图片)之后,会生成两个东西:Dom树和渲染树。

1. 重排(重新排列 reflow)

当页面的布局和几何属性发生改变的时候,就需要进行重排

以下的情况也同样会发生重排:

  • 添加和或者删除可见的DOM元素
  • 元素的位置发生变化
  • 元素的尺寸发生变化(包括:外边距,内边距,边框厚度,宽度,高度等属性发生改变)
  • 内容发生变化(例如:内容增加引起高度变化或者是图片被另外一个不同尺寸的图片所替换)
  • 页面渲染器进行初始化的
  • 浏览器窗口尺寸发生改变

2. 重绘(重新绘制 repaint)

在一个元素的外观被改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。

color,background-color

重排必定会引发重绘,但重绘不一定会引发重排。

3. 哪些实际操作会导致回流与重绘

3.1 最“贵”的操作:改变 DOM 元素的几何属性

3.2 “价格适中”的操作:改变 DOM 树的结构

3.3 最容易被忽略的操作:获取一些特定属性的值

  • 当你要用到像这样的属性:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight 时,你就要注意了!

  • “像这样”的属性,到底是像什么样?——这些值有一个共性,就是需要通过即时计算得到。因此浏览器为了获取这些值,也会进行回流。

  • 除此之外,当我们调用了 getComputedStyle 方法,或者 IE 里的 currentStyle 时,也会触发回流。原理是一样的,都为求一个“即时性”和“准确性”。

4. 如何规避回流与重绘

4.1 将引起重绘的行为缓存起来,避免频繁改动

  • 不要把DOM结点的属性值放在一个循环里当成循环里的变量。
  • 可以使用防抖、节流
  • 尽量不要使用table布局。
  • 如果需要创建多个DOM节点,可以使用DocumentFragment创建完后一次性的加入document;
var fragment = document.createDocumentFragment();
 
var li = document.createElement('li');
li.innerHTML = 'apple';
fragment.appendChild(li);
 
var li = document.createElement('li');
li.innerHTML = 'watermelon';
fragment.appendChild(li);
 
document.getElementById('fruit').appendChild(fragment);
  • 为动画的HTML元件使用position: fixed/absolute,那么修改他们的css是不会reflow
    • 元素脱离了文档流,它的变化不会影响到其他元素

4.2 避免逐条改变样式,使用类名去合并样式

  • 不要一条一条的修改DOM的样式,可以先定义好css的class,然后修改DOM的className。
var el = document.getElementById('mydiv');
el.style.width = '300px'; 
el.style.height = '400px';
el.style.margin = '15px';

4.3 将 DOM “离线”

我们上文所说的回流和重绘,都是在“该元素位于页面上”的前提下会发生的。一旦我们给元素设置 display: none,将其从页面上“拿掉”,那么我们的后续操作,将无法触发回流与重绘——这个将元素“拿掉”的操作,就叫做 DOM 离线化。

当我们只需要进行很少的 DOM 操作时,DOM 离线化的优越性确实不太明显。一旦操作频繁起来,这“拿掉”和“放回”的开销都将会是非常值得的。

5. 浏览器自己的优化:Flush 队列:浏览器并没有那么简单

因为现代浏览器是很聪明的。浏览器自己也清楚,如果每次 DOM 操作都即时地反馈一次回流或重绘,那么性能上来说是扛不住的。于是它自己缓存了一个 flush 队列,把我们触发的回流与重绘任务都塞进去,待到队列里的任务多起来、或者达到了一定的时间间隔,或者“不得已”的时候,再将这些任务一口气出队。

因此我们看到,上面就算我们进行了 4 次 DOM 更改,也只触发了一次 Layout 和一次 Paint。

大家这里尤其小心这个“不得已”的时候。前面我们在介绍回流的“导火索”的时候,提到过有一类属性很特别,它们有很强的“即时性”。当我们访问这些属性时,浏览器会为了获得此时此刻的、最准确的属性值,而提前将 flush 队列的任务出队——这就是所谓的“不得已”时刻。