跳到主要内容

JavaScript 的一个很重要的作用就是对 DOM 进行操作,但是对 DOM 的操作是非常占用资源的,因为这会导致浏览器执行 DOM Reflow 和 Repaint 操作。而执行了过多的页面重构操作,就会使网页变得越来越慢。

关于 DOM Reflow 和 Repaint

一个 HTML 网页被加载进浏览器中后,被解析为 DOM ,然后再应用样式来呈现,因此该页面的最终呈现通过两个步骤来完成:

  • DOM :描述该页面的结构
  • 渲染:描述 DOM 在页面上如何呈现

当 DOM 元素的属性(如 color )发生变化时,浏览器会通知渲染引擎重新描绘相应的元素,此过程称为 Repaint 。

如果变化涉及元素布局,浏览器则抛弃原有属性,重新计算并把结果传递给渲染引擎以重新描绘页面元素,此过程称为 Reflow 。

这两个步骤是最耗费资源的,每次对元素的操作都会发生 Repaint 或 Reflow ,因此编写 DOM 交互时如果不注意就会导致页面性能低下。

除了在首次加载页面时必然要经历以上这两个步骤之外,还有以下行为会触发这个行为:

  • DOM 元素的添加、修改、删除,此时 Reflow 和 Repaint 都会发生
  • 改变窗体大小、滚动页面、添加或移除样式、内容改变、 CSS 伪类被触发、更改元素的 className 、获取一个必须经过计算的尺寸值(如读取元素的 offsetLeft 、 offsetTop 、 offsetHeight 、 offsetWidth 、 scrollTop/Left/Width/Height 、 clientTop/Left/Width/ Height 、 getComputedStyle() 、 currentStyle 属性或者其它需要经过计算的 CSS 值),都会导致 Repaint 发生 在兼容 DOM 的浏览器中,可以通过 getComputedStyle() 方法获取样式的计算值。在 IE 中,可以通过 currentStyle 属性获取计算值。

此时只有 Repaint ,因为不需要调整布局。

优化 DOM Reflow

可通过以下方法优化 DOM Reflow :

  • 能直接使用 innerHTML 的就不要用 DOM , DOM 要比直接生成代码慢很多
  • 将元素从 document 中删除,完成修改后再把元素放回原来的位置
  • 将元素的 display 设置为 none ,完成修改后再把 display 修改为原来的值
  • 在使用 DOM 时,可以使用文档片段( document.createDocumentFragment() )缓存过程中的 DOM 元素

例如,在游戏的一个场景中,如果要为一个 Player 加血,并假定是 5 格血,就会是 5 个能量值来呈现,因此可能会使用下面的方法:

for (var i = 0; i < 5; i++) {
var powerCell = new PowerCell();
document.body.appendChild(powerCell);
}

问题出现了,我们的目的是希望能同时出现 5 格血,这里,如果循环将 5 个 PowerCell 对象添加到 body ,会导致一个结果:浏览器对 DOM 树重构了 5 次( DOM 树重构术语被称为 Reflow )。

这里就可以使用文档片段,先把 5 格血封装为一个 DocumentFragment ,然后,再一次性添加到 body 中,这样就只会 Reflow 一次,从而大大提高性能。例如下面的代码:

var df = document.createDocumentFragment();
for (var i = 0; i < 5; i++) {
var powerCell = new PowerCell();
df.appendChild(powerCell);
}
document.body.appendChild(df);

优化 Repaint

Repaint 其实就是 CSS 导致的,因此,要注意以下事项:

  • 尽可能少地修改元素 style 上的属性,尽量通过修改 className 来修改样式
  • 通过 cssText 属性一次性将样式值设置到对象
  • 避免设置过多的行内样式
  • 避免使用表格来布局