转:DOM性能瓶颈与Javascript性能优化

编程语言 Mr.R 3年前 (2016-11-23) 1153次浏览 已收录 1个评论 扫描二维码

一.Dom的性能瓶颈及原因

1. 为什么是DOM

标准的xml/html的文本解析协议,常见的有DOM与SAX。在解析速度及内存占用上,SAX比DOM有优势,但为什么浏览器选择DOM解析html

(1)DOM VS SAX

SAX提供一次性解析文本,不生成对象,Iterator模式访问元素,event-based,PUSH模式触发,简单说:App需要向Parser注册,当Parser遍历xml时,触发调用APP 。想深入体验,用下javax.xml.parsers.SAXParser。这里说个题外话,改进版StAX是PULL模式,但这都不重要了,重要是:一次性文本解析,不生成对象。

DOM解析文本后,生成DOM树。即:一次性文本解析,生成对象。

(2)浏览器选择了DOM

单次效率DOM不如SAX,但SAX不生成对象,浏览器很多操作很难满足,比如:元素定位,元素样式渲染……所以DOM是必然之选。

 

2. DOM的性能问题

【1】核心问题

当解析的html文件很大时,生成DOM树占用内存较大,同时遍历(不更新)元素耗时也更长。但这都不是重点,DOM的核心问题是:DOM修改导致的页面重绘、重新排版!重新排版是用户阻塞的操作,同时,如果频繁重排,CPU使用率也会猛涨!

DOM操作会导致一系列的重绘(repaint)、重新排版(reflow)操作。为了确保执行结果的准确性,所有的修改操作是按顺序同步执行的。大部分浏览器都不会在JavaScript的执行过程中更新DOM。相应的,这些浏览器将对对 DOM的操作放进一个队列,并在JavaScript脚本执行完毕以后按顺序一次执行完毕。也就是说,在JavaScript执行的过程,直到发生重新排版,用户一直被阻塞。

    一般的浏览器中(不含IE),repaint的速度远快于reflow,所以避免reflow更重要

导致repaint、reflow的操作

 * DOM元素的添加、修改(内容)、删除( Reflow + Repaint)

    * 仅修改DOM元素的字体颜色(只有Repaint,因为不需要调整布局)

    * 应用新的样式或者修改任何影响元素外观的属性

    * Resize浏览器窗口、滚动页面

    * 读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE))

【2】其他

某些Javascript框架中,CSS选择器,如:var el = $(‘.hyddd’);由于IE6、7不支持,所以Javascript框架必须通过遍历整个DOM树来寻找对象。

 

二. 针对DOM问题,Javascript的应对方案

1. 针对repaint、reflow,Nicholas 大叔在他的《Speed up your JavaScript, Part 4》做了详细介绍,这里我也整理一下:

解决问题的关键是:减少因DOM操作,引起的reflow。Nicholas总结了一些方法:

【1】在DOM外,执行尽量多的变更操作。Demo:

// 不好的做法
for (var i=0; i < items.length; i++){
    var item = document.createElement("li");
    item.appendChild(document.createTextNode("Option " + i);
    list.appendChild(item);
}
// 更好的做法
// 使用容器存放临时变更, 最后再一次性更新DOM
var fragment = document.createDocumentFragment();
for (var i=0; i < items.length; i++){
    var item = document.createElement("li");
    item.appendChild(document.createTextNode("Option " + i);
    fragment.appendChild(item);
}
list.appendChild(fragment);

【2】操作DOM前,先把DOM节点删除或隐藏,因为隐藏的节点不会触发重排。Demo如下:

list.style.display = "none";  
for (var i=0; i < items.length; i++){  
    var item = document.createElement("li");  
    item.appendChild(document.createTextNode("Option " + i);  
    list.appendChild(item);  
}  
list.style.display = "";

【3】一次性,修改样式属性。Demo如下:

// 不好的做法
// 这种做法会触发多次重排
element.style.backgroundColor = "blue";  
element.style.color = "red";  
element.style.fontSize = "12em";
// 更好的做法是,把样式都放在一个class下
.newStyle {  
    background-color: blue;  
    color: red;  
    font-size: 12em;  
}  
element.className = "newStyle";

【4】使用缓存,缓存临时节点。

// 不好的做法
document.getElementById("myDiv").style.left = document.getElementById("myDiv").offsetLeft +  
document.getElementById("myDiv").offsetWidth + "px";
// 更好的做法
var myDiv = document.getElementById("myDiv");  
myDiv.style.left = myDiv.offsetLeft + myDiv.offsetWidth + "px";

 

2. 针选择其的问题,我只能说:没办法…… : (

 

三. 参考资源

1.《Web 2.0应用客户端性能问题十大根源

2. 《Speed up your JavaScript, Part 4

 

 

 

现在的web应用越来越复杂,需要响应各种各样的用户触发事件,因而也就不可避免的,需要给我们的html页面上的dom元素增加事件监听函数。

我们知道给dom元素绑定事件监听函数的方法有如下3种:

1. 页面html:

<button onclick=”test();”></button>

 

2. 页面html:

<button id=”btn”></button>

Javascript:

document.getElementById(“btn”).onclick = test;

3. 页面html:

<button id=”btn”></button>

Javascript:

document.getElementById(“btn”).atachEvent(“onclick”,test); //ie

这3种方法的功能效果和差异,大家都了解,在此就不在赘述了。但是这3种方法,对页面渲染的速度,资源的消耗,却是有很大不同的。

正文后面的html代码是一个demo页面,大家可以用ie浏览器打开,通过注释不同的代码段,查看页面运行效果。

可以看到第一种方式的效率是最低的,随着页面节点的增多,页面渲染时间急剧增加,在ie7下运行,大概670ms。

第二种方式明显好一些,在ie7下,大概250ms。

而第三种方式则是最快的方法,也是web前端开发推荐的标准写法,在ie7下,大概188ms。

然后我们去掉事件绑定的逻辑,发现只渲染dom元素,不绑定事件的时间,仅仅125ms。可见事件绑定的时间消耗还是很大的,尤其是第一种方式,也就是Dom Level 0 Event,最为耗时。

另外,大家运行各段代码的时候,不妨打开任务管理器,找到浏览器对应的进程,查看代码运行时cpu的消耗以及内存的使用。

我们可以看到,Dom Level 0 Event,对cpu的消耗明显要高很多。

对内存的消耗分析:

重新打开浏览器,空白页面的内存占用量大概是37M,虚拟内存为28M,页面渲染后:

1. 内存使用 54M,虚拟内存41M;

2. 内存使用44M,虚拟内存31M;

3. 内存使用44M,虚拟内存31M。

可见Dom Level 0 Event对内存的消耗,也远远超出了其它方式。

为什么Dom Level 0 Event会这么消耗系统资源呢?对cpu和内存的消耗都远远超出了其它方式,我们来做一个简单分析。

为了便于分析,我们不妨修改一下我们的代码 <button onclick=”debugger;test();”></button> ,然后运行页面,在ie的script debugger里我们找到堆栈调用这一项,可以看到有一个anonymos function,这个function是从何而来的呢?原来浏览器在对Dom Level 0 Event做绑定的时候,会自动生成一个包含我们的代码的匿名函数,然后把这个匿名函数绑定到事件,类似于如下方式:

document.getElementById(“btn”).onclick = function(event){
test();
} ;

而ie浏览器又没有足够的智能,区分出众多内部功能完全一致的匿名函数,并合并它们的引用,所以导致了随着dom事件绑定的越来越多,匿名函数的个数也越来越多。因为要声明数量众多的事件处理匿名函数,也就不难明白,为什么会消耗如此多的系统资源了。

随着dom元素的增多,这个资源消耗就会越来越严重。而且我们可以尝试着刷新一下页面,发现随着刷新的次数增加,页面运行越来越慢,cpu消耗也越来越多,内存也会有少量增加。可见,Dom Level 0 Event 还会带来少量的内存泄露,至于时间的延长,cpu消耗的加剧,推测是因为浏览器忙于释放众多的匿名函数所占用的资源所带来的后果。

进一步深入,由于ie浏览器是基于冒泡的事件模型,子元素的event会冒泡到父元素,所以更极致的优化是去掉众多子元素的事件绑定,而将事件绑定到父元素。在正文后的demo中,也有这方面的尝试,可以看到不仅cpu,内存消耗最低,时间上也跟渲染干净的html页面是一样的。

所以,我们在页面事件绑定中,要尽量避免Dom Level 0 Event,而且要尽可能的将事件上升(当然也要考虑事件处理的灵活性)。

demo:

<BODY>
<ul id=”list”></ul>
<SCRIPT LANGUAGE=”JavaScript”>
<!–
var $ = function(id){
return document.getElementById(id)
};
function test(){
alert(1)
}
var ul = $(“list”);
var count = 5000;
// ie7
//–>
</SCRIPT>
<script>
var d = new Date()
var str = [];
for(var i = 0;i<count;i++){
str.push(‘<li onclick=”test();”>’+i+'</li>’)
}
ul.innerHTML = str.join(“”);
alert(new Date – d);
//670 刷新时时间增加 85
</script>
<SCRIPT LANGUAGE=”JavaScript”>
<!–
/*var d = new Date()
var str = [];
for(var i = 0;i<count;i++){
str.push(‘<li>’+i+'</li>’)
}
ul.innerHTML = str.join(“”);
alert(new Date – d); */
//125
//–>
</SCRIPT>
<SCRIPT LANGUAGE=”JavaScript”>
<!–
/*var d = new Date()
var str = [];
for(var i = 0;i<count;i++){
str.push(‘<li>’+i+'</li>’)
}
ul.innerHTML = str.join(“”);
var li = document.getElementsByTagName(“li”);
var l = li.length;
for(var i=0;i<l;i++){
li[i].onclick = test;
}
li = null;
alert(new Date – d);*/
//250
//–>
</SCRIPT>
<SCRIPT LANGUAGE=”JavaScript”>
<!–
/*var d = new Date()
var str = [];
for(var i = 0;i<count;i++){
str.push(‘<li>’+i+'</li>’)
}
ul.innerHTML = str.join(“”);
var li = document.getElementsByTagName(“li”);
var l = li.length;
for(var i=0;i<l;i++){
li[i].attachEvent(“onclick”,test);
}
li = null;
alert(new Date – d);*/
//188
//–>
</SCRIPT>
<SCRIPT LANGUAGE=”JavaScript”>
<!–
/*var d = new Date()
var str = [];
for(var i = 0;i<count;i++){
str.push(‘<li>’+i+'</li>’)
}
ul.innerHTML = str.join(“”);
ul.attachEvent(“onclick”,test);
alert(new Date – d);*/
//125
//–>
</SCRIPT>
</BODY>

 


QQ㊀群: 240424174;QQ㊁群: 749250816;QQ㊂群: 542382043;
㊄群: 860166303;⑥群: 738134262 ⑦群 750397184
常见问题:1、文件解压请使用Winrar5.5版本以上解压;
2、视频播放有声音无画面,请了解下PotPlayer播放器;
3、回复仍不可见隐藏内容,请使用邮箱注册登录;
R大技术站 , 版权所有,如未注明 , 均为原创,如需转载请注明来源!
喜欢 (0)
[itdiy@qq.com]
分享 (0)
发表我的评论
取消评论

表情 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
(1)个小伙伴在吐槽
  1. 今天办公室里几个男女在讨论用避孕套的好处和坏处,男方坚决抵制避孕套,女方大力支持使用避孕套,双方各持一词不分伯仲,这时我们办公室一个大姐问我平日用套套吗?我说:我……我...我都是包皮打个结。当时讨论就结束了。
    增达网2016-12-01 10:41 回复 Windows 7 | 搜狗浏览器 2.X