一、渲染引擎的分离
浏览器中通常会把DOM和JavaScript引擎独立实现。
(1)IE:javascript实现是JScript,DOM实现为Trident。
(2)Safari:javascript引擎SquirrelFish,DOM渲染采用的是Webkit中的WebCore实现的。
(3)Chrome:javascript引擎为V8,DOM渲染采用的是Webkit中的WebCore实现的。
(4)Firefox:javascript引擎名为SpiderMonkey,新版名为TraceMonkey,DOM渲染采用的是Gecko 。
二、分离的后果
两个相互独立的功能只能通过接口彼此连接,但是这样会产生性能消耗,这好比两个独立的岛屿只能通过桥梁来沟通而不幸的是这个桥梁是要收费的。
DOM访问次数越多,代码运行速度越慢。所以一般经验法则是:减少DOM的次数,把运算尽量保留在ECMAScript这一块处理。
三、HTML页面绘制过程
浏览器下载完页面中所有组件(html、js、css)后,解析生成两个内部数据结构:DOM树(表示页面结构)、渲染树(表示DOM节点如何显示)。
DOM树中每一个需要显示的节点在渲染树中至少存在一个对应的节点(隐藏的DOM元素在渲染树中没有对应的节点)。渲染树中的节点被称为“帧”或者“盒”,可理解为页面元素中一个具有padding、margin、border、position的盒子。一旦DOM和渲染树构建完成,浏览器就开始绘制页面元素。
四、重绘(repaint)和重排(repaint)
当DOM的变化影响元素的几何属性(宽和高),浏览器需要重新计算元素的几何属性,同样其他元素的几何属性和位置也会受到影响。
浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树,这个过程称为重排。 完成重排后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘 。
并不是所有的DOM变化都会影响几何属性(如背景的改变),这样只会发生重绘。
五:重排何时发生?
1、添加或删除可见的DOM元素 ;
2、元素位置的改变 ;
3、元素尺寸改变 ;
4、内容改变 ;
5、页面渲染器初始化;
6、浏览器窗口尺寸的改变 。
六、渲染树变化的排队与刷新
由于每次重排都会产生计算消耗,所以大多数浏览器都通过队列化修改并批量执行来优化重排过程。
但是有的属性和方法需要返回最新的布局信息,因此浏览器不得不处理渲染队列中的"待处理变化"并触发重排以返回最新的值,这样就导致了队列的刷新。这些属性有:
offsetTop、offsetLeft、offsetWidth、offsetHeight;
scrollTop、scrollLeft、scrollWidth、scrollHeight;
clientTop、clientLeft、clientWidth、clientHeight;
getComputedStyle()(currentStyle in IE)。
七、最小化重绘和重排
重绘和重排代价可能非常昂贵,因此一个好的提高程序响应速度的策略就是减少此类操作的发生。为了减少发生次数,应该多次合并对DOM和样式的修改,然后一次处理掉。
1、改变样式:
(1)利用cssText属性合并所有改变,然后一次性写入。
(2)修改html元素中对应的class名,利用class替换样式(不依赖于运算逻辑和计算的情况下)。
2、批量修改DOM:
(1)步骤:使元素脱离文档流 ->对其应用多重改变 -> 把元素带回到文档中。
(2)脱离文档流的方式:
a、隐藏元素,应用修改,重新显示;
b、使用文档片段(document fragment)在当前DOM之外构建一个子树,再插回去;
c、将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后再替换回去;
(3)实例: 假设我们需要用已有数据对一个已有的ul列表插入子项。html片段为:
<ul id="list"> <li><a href="http://www.baidu.com">百度</a></li> </ul>
数据为:
var data = [ { "name":"Google", "utl":"http://google.com" }, { "name":"Apple", "utl":"http://apple.com" } ]
更新节点的通用函数:
function appendDataToElement(node,data){ var a,li,doc = document for(var i=0,max = data.length;i<max;i++){ a = doc.createElement('a'); a.href=data[i].url; a.appendChild(doc.createTextNote(data[i].name)); li = doc.createElement('li'); li.appendChild(a); node.appendChild(li); } }
最常用但低效的执行:
var list = document.getElementById("list"); appendDataToElement(list,data);
使用display属性减少重排:
var list = document.getElementById("list"); list.style.display = 'none'; appendDataToElement(list,data); list.style.display = 'block';
使用文档片段减少重排(推荐):
var list = document.getElementById("list"); var fragment = document.createDocumentFragment(); appendDataToElement(fragment,data); list.appendChild(fragment);
采用副本方式减少重排:
var old = document.getElementById("list"); var clone = old.cloneNode(true); appendDataToElement(clone,data); old.parentNode.replaceChild(clone,old);