原文地址--->
最近做项目时遇到了页面加载卡顿问题,一时没有头绪,感到无从下手,看到这篇文章,所以整体梳理了一下,在此记录。
在富客户端网页应用中,界面上的UI的更改是通过DOM操作实现的。
尽管DOM提供了丰富接口供外部调用,但是dom操作的代价很高,
页面前端代码的性能瓶颈大多集中在DOM操作上,因此,前端性能优化的
一个主要的关注点是dom操作的优化,因此我们可以想办法通过尽量减少
DOM操作来优化性能。
首先,DOM操作为什么会影响性能。在浏览器中,DOM的实现和ECMAScript
的实现是分离的。例如,在IE中,ECMAScript的实现在jscript.dll中,而DOM的
实现在mshtml.dll中;在chrome中使用webkit的WebCore处理DOM和渲染,但
ECMAScript是在V8引擎中实现的,其他浏览器的情况类似,所以通过Javascript
调用dom接口,是相当于两个模块的交互。相比较在同一模块中的调用,这种跨模块的
调用其性能损耗是很高的,但DOM操作对性能影响最大是因为它导致了浏览器的重绘
和重排。
浏览器渲染原理的简单说明
从下载文档到渲染页面的过程中,浏览器会通过解析HTML文档来构建DOM树,解析
CSS产生CSS规则树。javascript在代码解析的过程中,可能会修改生成的dom树
和css规则树,之后根据dom树和css规则树构建渲染树,在这个过程中css会根据
选择器匹配HTML元素。渲染树包括了每个元素的大小,边距等样式属性。渲染树
中不包含隐藏元素及head元素等不可见元素。最后浏览器根据元素的坐标和大小
来计算每个元素的位置,并绘制这些元素到页面上。重绘指的是元素的位置或尺寸
发生了改变,浏览器会重新绘制页面上受影响的元素,重排的代价高于重绘。
之下的DOM操作会导致重绘或重排:
1.增加,删除和修改可见DOM元素
2. 页面初始化的渲染
3.移动dom元素
4.修改css样式,改变dom元素的尺寸
5.dom元素内容改变,使得尺寸被撑大
6.浏览器窗口尺寸改变
7.浏览器窗口滚动
现代浏览器会针对重排或重绘做性能优化,例如,把DOM操作积累一批后统一
做一次重排或重绘,但在有些情况下,浏览器会立即进行重排或重绘,比如请求如下的
dom元素布局信息
offsetTop/Left/Width/Height
scrollTop/Left/Width/Height
clientTop/Left/Width/Height
getComputeStyle()或currentStyle
因为这些值都是动态计算的,所以浏览器需尽快完成页面绘制,计算返回值,打乱了重绘或
重排优化。
可以遵循一些最佳实践来降低影响-->
方法一:合并多此dom操作为单次dom操作
通过class类名来元素的大量样式更改,代码维护性较好。
通过innerHTML接口来修改DOM元素的内容时,以字符串方式拼接好代码后,一次性赋值给
DOM元素的innerHTML接口。
方法二: 把DOM元素离线或隐藏后修改
把元素从页面流中脱离或隐藏,这样处理后,只会在DOM元素脱离或添加时,或者是隐藏或显示时才会造成页面的重绘会重排,对脱离了页面布局的DOM元素操作就不会导致页面的性能问题。
这种方式适合需要大批量修改dom元素的情况。具体方式由三种:
(1) 使用文档片段
文档片段是一个轻量级的document对象,并不会和特定的页面关联,通过在文档片段上进行DOM操作,可以降低DOM操作对页面性能的影响,这种方式是创建一个文档片段,并在此片段上
进行必要的DOM操作,操作完成后将它附加在页面中,对页面的影响只存在于最后把文档片段附加到页面的这一步操作上。
var fragment=document.createDocumentFragment();
//一些基于fragment的大量dom操作
...
document.getElementById('myElement').appendChild(fragment);
(2)隐藏元素
通过隐藏元素,达到在页面上移除元素的效果,经过大量的DOM操作后恢复元素原来的display样式,只有隐藏和显示元素时会引起页面重绘或重排操作。
var myElement=document.getElementById('myElement');
myElement.style.display='none';//dom操作
myElement.style.display='block';
(3)克隆DOM元素到内存中
把页面上的DOM元素克隆一份到内存中,然后在内存中操作克隆的元素,操作完成后使用此克隆元素替换页面中原来的DOM元素,只有替换元素时会影响性能,在内存中操作克隆元素不会引起页面上的性能损耗。
var old=document.getElementById('myElement');
var clone=old.cloneNode(true);
//dom操作
old.parentNode.replaceChild(clone,old);
3.设置具有动画效果的DOM元素的position属性为fixed或absolute
把页面中具有动画效果的元素设置为绝对定位,使得元素脱离页面布局流,从未避免了页面频繁的重排,只涉及动画元素自身的重排。这种做法可以提高动画效果的展示性能。(在动画开始时将其设置为绝对定位,等动画结束后恢复原始的定位设置)。
4.谨慎获得dom元素的布局信息,变量本地化。
把获取到的元素的布局信息值缓存在局部变量中。在有大量的DOM操作时,避免获取dom元素的布局信息,如果需要布局信息,最好在DOM操作之前就取好存放。
5.使用事件托管方式绑定事件
在DOM元素上绑定事件或影响页面的性能,一方面,绑定事件本身会占用处理时间,另一方面,浏览器保存事件绑定也会占用内存。使用事件托管方式,即利用事件冒泡机制,只在父元素上绑定事件处理,用于处理所有子元素的事件,在事件处理函数中根据传入的参数判断事件源元素,针对不同的元素做不同的处理。这种方式有很大的灵活性,可以方便的添加或删除子元素,不需要考虑因元素移除或添加需要修改事件绑定。