高性能javascript笔记:浏览器中DOM操作的性能优化(二)
关于浏览器中DOM操作的性能优化,在上一篇博文《浏览器中DOM操作的性能优化(一)》中已经阐述了访问和修改DOM元素对性能的影响及优化方案。这次我们就来说一下关于页面的重绘和重排版问题。
当浏览器下载完所有的HTML标签和其组件(Javascript,css,图片等)后,浏览器就会解析文件并创建两个内部数据结构:
1、DOM Tree :表示页面的结构
2、Render Tree :表示DOM树如何显示
在渲染树仲,每个DOM节点(隐藏的节点除外)都有至少一个相对应的节点。渲染树的节点被称为“盒”,符合CSS定义的盒子模型——一个具有填充(padding)、边框(border)、边距(margin)和位置(position)的盒子。一旦DOM树和渲染树构造完毕,浏览器就可以显示(绘制)页面上的元素。
当DOM的元素几何属性(宽和高)改变的时候,浏览器就是重新计算元素的几何属性,而其它元素的几何属性和位置也可能会因此受到影响,对应的渲染树部分因此失效,浏览器就不得不重构渲染树,这个过程就叫做重排版。重排版完成时,浏览器会在一个重绘进程中绘制屏幕上受影响的部分。而如果没有改变元素的几何属性(如仅改变元素的背景),元素的布局没有受到影响,这个过程就叫做重绘。下述情况中会发生重排版:
- 添加或删除可见的元素
- 元素的位置改变
- 元素的尺寸改变
- 元素的内容改变
- 浏览器窗口大小改变
调用某些方法或访问某些属性也会引发重排版,因为需要计算布局信息,所以浏览器不得不运行渲染队列中待改变的项目并重新排版以返回正确的值。所以当要用到下列的属性或方法时,最好用局部变量缓存它们
- offsetTop、offsetLeft、offsetWidth、offsetHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTop、clientLeft、clientWidth、clientHeight
- document.defaultView.getComputedStyle() (IE中对应的元素属性是currentStyle)
既然重绘和重排版会带来性能影响,我们就应该避免次情况发生的次数。
修改style属性
考虑下面的这个列子:
var el = docuemnt.getElementById("mydiv"); el.style.borderLeft = "1px"; el.style.margin = "5px"; el.style.padding = "5px";
可以优化成如下代码:
var el = document.getElementById("mydiv"); el.style.cssText = ";border:1px;margin:5px;padding:5px";//注意前面有个分号,避免覆盖之前的样式
DOM元素的批量修改
当你需要对DOM元素进行多次修改时,可通过以下的步骤减少重绘和重排版的次数:
- 从文档流中移除该DOM元素(引发重排版)
- 对该元素进行多次修改(如果其它两步,则每一次修改都会导致重排版)
- 将元素带回文档中显示(引发重排版)
有二种方法可以将DOM元素从文档流中移除:
- 隐藏元素,进行修改,然后再显示
- 新创建或克隆节点,进行修改,然后覆盖原始的DOM元素
下面就举个列子说明(更新列表):
HTML code:
<ul id="mylist"> <li><a href="http://www.baidu.com">baidu</a></li> <li><a href="http://www.google.com.hk">google</a></li> </ul>
Javascript code:
//要添加的数据 var data = [ { name:"cnblogs", url:"http://www.cnblogs.com/" }, { name:"leolai", url:"http://www.cnblogs.com/leolai/" } ]; //创建一个通用的函数,用于将数据添加到指定的列表中 function appendDataToList(targetElement, data){ var a, li, item; for(var i=0,len=data.length; i<len; i++){ item = data[i]; a = document.createElement("a"); a.src = item.url; a.appendChild(document.createTextNode(item.name)); li = document.createElement("li"); li.appendChild(a); targetElement.appendChild(li); } } //如果不理会重排版问题,将数据更新到列表中最简单的方法如下 var ul = document.getElementById("mylist"); appendDataToList(ul,data); //使用这个方法,当data中每个项目追加到ul中都会导致重排版 //所以根据上面提到的优化方法,可修改成如下代码
//修改一:先隐藏,再修改,最后显示 var ul = document.getElementById("mylist"); ul.style.display = "none"; appendDataToList(ul,data); ul.style.display = "block"; //修改二:利用文档片段做一次性更新(推荐使用) var fragment = document.createDocumentFragment(); appendDataToList(fragment,data); document.getElementById("mylist").appendChild(fragment); //修改三:克隆,修改副本,覆盖 var ul = document.getElementById("mylist"); var clone = ul.cloneNode(true); appendDataToList(clone,data); ul.parentNode.replaceChild(clone,ul);
另外,我们经常在网页上做一些动画效果,对于这些动画元素应使用绝对定位,因为绝对定位不存在于文档流,所以它的改变不会影响到页面的布局。
还有,如果大量元素使用了:hover会降低反应速度。此问题在ie8中更为显著。