`

(转)如何提升JavaScript的运行速度(DOM篇)

阅读更多
http://www.yaohaixiao.com/
我们都知道,DOM操作的效率是很低的,而且不是一般的慢,而且这也是引发性能问题的常见问题之一。为什么会慢呢?因为对DOM的修改为影响网页的用户界面,重绘页面是一项昂贵的操作。太多的DOM操作会导致一系列的重绘操作,为了确保执行结果的准确性,所有的修改操作是按顺序同步执行的。我们称这个过程叫做回流(reflow),同时这也是最昂贵的浏览器操作之一。回流操作主要会发生在几种情况下:

* 当对DOM节点执行新增或者删除操作时。 * 动态设置一个样式时(比如element.style.width="10px")。 * 当获取一个必须经过计算的尺寸值时,比如访问offsetWidth、clientHeight或者其他需要经过计算的CSS值(在兼容DOM的浏览器中,可以通过getComputedStyle函数获取;在IE中,可以通过currentStyle属性获取)。 解决问题的关键,就是限制通过DOM操作所引发回流的次数。大部分浏览器都不会在JavaScript的执行过程中更新DOM。相应的,这些浏览器将对对 DOM的操作放进一个队列,并在JavaScript脚本执行完毕以后按顺序一次执行完毕。也就是说,在JavaScript执行的过程中,用户不能和浏览器进行互动,直到一个回流操作被执行。(失控脚本对话框会触发回流操作,因为他执行了一个中止JavaScript执行的操作,此时会对用户界面进行更新)

如果要减少由于DOM修改带来的回流操作,有两个基本的方法。第一个就是在对当前DOM进行操作之前,尽可能多的做一些准备工作。一个经典的例子就是向document对象中添加很多DOM节点:

/*
for (var i=0; i < items.length; i++){
   var item = document.createElement("li");
   item.appendChild(document.createTextNode("Option " + i);
   list.appendChild(item);
}
*/这段代码的效率是很低的,因为他在每次循环中都会修改当前DOM结构。为了提高性能,我们需要将这个次数降到最低,对于这个案例来说,最好的办法是建立一个文档碎片(document fragment),作为那些已创建元素元素的临时容器,最后一次将容器的内容直接添加到父节点中:

/*
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);
*/经过调整的代码,只会修改一次当前DOM的结构,就在最后一行,而在这之前,我们用文档碎片来保存那些中间结果。因为文档碎片没有任何可见内容,所以这类修改不会触发回流操作。实际上,文档碎片也不能被添加到DOM中,我们需要将它作为参数传给appendChild函数,而实际上添加的不是文档碎片本身,而是它下面的所有子元素。

避免不必要回流操作的另外一种方法,就是在对DOM操作之前,把要操作的元素,先从当前DOM结构中删除。对于删除一个元素,基本有两种方法:

1. 通过removeChild()或者replaceChild()实现真正意义上的删除。 2. 设置该元素的display样式为“none”。 而一旦修改操作完成,上面这个过程就需要反转过来,将删除的元素重新添加到当前的DOM结构中,我们还是拿上面的例子来做说明:

/*
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 = "";
*/将list的display样式设置为“none”后,就将这个元素从当前的DOM结构中删除了,因为这个节点不再可视。在将display属性设置回之前的默认值之前,向其下添加子元素是不会触发回流操作的。

另外一个经常引起回流操作的情况是通过style属性对元素的外观进行修改。比如下面这个例子:

/*
element.style.backgroundColor = "blue";
element.style.color = "red";
element.style.fontSize = "12em";
*/这段代码修改了三个样式,同时也就触发了三次回流操作。每次修改元素的style属性,都肯定会触发回流操作。如果你要同时修改一个元素的很多样式,最好的办法是将这些样式放到一个class下,然后直接修改元素的class,这可比单独修改元素的样式要强得多。比如下面这个例子:

/*
.newStyle {
   background-color: blue;
   color: red;
   font-size: 12em;
}
*/这样我们在JavaScript代码中,只需下面这行代码就可以修改样式:

/*
element.className = "newStyle";
*/修改元素的class属性,会一次将所有的样式应用在目标元素上,而且只会触发一次回流操作。这样做不止更加有效,而且还更容易维护。

既然DOM几乎在所有情况下都很慢,就很有必要将获取的DOM数据缓存起来。这种方法,不仅对获取那些会触发回流操作的属性(比如offsetWidth等)尤为重要,就算对于一般情况,也同样适用。下面介绍一个效率低的夸张的例子:

/*
document.getElementById("myDiv").style.left = document.getElementById("myDiv").offsetLeft +
   document.getElementById("myDiv").offsetWidth + "px";
*/这里对getElementById()调用了三次,是一个很大的问题,访问DOM是很昂贵的,而这三个调用恰恰访问的是同一个元素,也许我们像下面这样写,会更好一些:

/*
var myDiv = document.getElementById("myDiv");
myDiv.style.left = myDiv.offsetLeft + myDiv.offsetWidth + "px";
*/我们去掉了一些冗余操作,现在对DOM操作的次数已经被减小了。对于那些使用次数超过一次的DOM值,我们都应该缓冲起来,这样可以避免无谓的性能消耗。

也许,拖慢属性访问速度的罪魁祸首就是HTMLCollection对象。这些对象是object类型的,只要DOM需要返回一组节点时就会使用这个对象,也就是说childNodes属性和getElementsByTagName()的返回值都属于这种情况。我们可能经常会将 HTMLCollection当作数组来使用,但实际上他是一个根据DOM结构自动变化的实体对象。每次你访问一个HTMLCollection对象的属性,他都会对DOM内所有的节点进行一次完整匹配,这意味着下面的代码将导致一个死循环:

/*
var divs = document.getElementsByTagName("div");
for (var i=0; i < divs.length; i++){  //infinite loop
   document.body.appendChild(document.createElement("div"));
}
*/这段代码为什么会变成死循环呢?因为在每次循环中,将会向document中新增一个div元素,同时也会更新divs这个集合,也就是说循环的索引永远都不会超过divs.length的值,因为divs.length的值是伴随着循环而递增的。每次访问divs.length,就会更新一次集合对象,这可比访问一个普通数组的length属性要付出更大的代价。当对HTMLCollection对象进行操作时,应该将访问的次数尽可能的降至最低,最简单的,你可以将length属性缓存在一个本地变量中,这样就能大幅度的提高循环的效率。

/*
var divs = document.getElementsByTagName("div");
for (var i=0, len=divs.length; i < len; i++){  //not an infinite loop
   document.body.appendChild(document.createElement("div"));
}
*/修改后的代码已经不是死循环了,因为在每次循环时,len的值都是保持固定不变的。将属性值缓存起来除了更加有效率,还可以保证document不会执行多于一次的查询。


分享到:
评论

相关推荐

    如何提升JavaScript的运行速度(DOM篇).doc

    如何提升JavaScript的运行速度(DOM篇).doc

    JavaScript 提升运行速度之循环篇 译文

    根据Nicholas 的说法,有四种代码 会拖慢脚本的运行,并最终导致脚本失控。分别是次数过多的同步循环、庞大的函数体、不恰当的递归和不合理的DOM 调用。

    高性能JavaScript 编程pdf电子书(中英文对译)

    你将会了解如何提升各方面的性能,包括代码的加载、运行、DOM交互、页面生存周期等。雅虎的前端工程师Nicholas C. Zakas和其他五位JavaScript专家介绍了页面代码加载的最佳方法和编程技巧,来帮助你编写更为高效和...

    高性能JavaScript

    资源名称:高性能JavaScript内容...你将会了解如何提升各方面的性能,包括代码的加载、运行、DOM 交互、页面生存周期等。雅虎的前端工程师 Nicholas C. Za 资源太大,传百度网盘了,链接在附件中,有需要的同学自取。

    高性能JavaScript编程(中英文)

    你将会了解如何提升各方面的性能,包括代码的加载、运行、DOM交互、页面生存周期等。雅虎的前端工程师Nicholas C. Zakas和其他五位JavaScript专家介绍了页面代码加载的最佳方法和编程技巧,来帮助你编写更为高效和...

    高性能JavaScript 高清完整.pdf版下载

    你将会了解如何提升各方面的性能,包括代码的加载、运行、DOM交互、页面生存周期等。雅虎的前端工程师Nicholas C. Zakas和其他五位JavaScript专家介绍了页面代码加载的最佳方法和编程技巧,来帮助你编写更为高效和...

    高性能JavaScript(High Performance JavaScript )

    你将会了解如何提升各方面的性能,包括代码的加载、运行、DOM 交互、页面生存周期等。雅虎的前端工程师 Nicholas C. Zakas 和其他五位 JavaScript 专家介绍了页面代码加载的最佳方法和编程技巧,来帮助你编写更为...

    高性能JavaScript编程

    你将会了解如何提升各方面的性能,包括代码的加载、运行、DOM交互、页面生存周期等。雅虎的前端工程师Nicholas C. Zakas和其他五位JavaScript专家介绍了页面代码加载的最佳方法和编程技巧,来帮助你编写更为高效和...

    高性能JavaScript_中英双语版.rar

    你将会了解如何提升各方面的性能,包括代码的加载、运行、DOM交互、页面生存周期等。雅虎的前端工程师Nicholas C. Zakas和其他五位JavaScript专家介绍了页面代码加载的最佳方法和编程技巧,来帮助你编写更为高效和...

    程序天下:JavaScript实例自学手册

    14.15 在JavaScript运行VBScript函数 14.16 购物篮中常用的计算总价效果 14.17 同一用户的来访统计 14.18 十六进制转换为十进制 14.19 将URL转化为16进制 14.20 小写金额转换为大写 14.21 通过两点坐标计算直线距离 ...

    高性能Javascript编程--英文版

    你将会了解如何提升各方面的性能,包括代码的加载、运行、DOM交互、页面生存周期等。雅虎的前端工程师Nicholas C. Zakas和其他五位JavaScript专家介绍了页面代码加载的最佳方法和编程技巧,来帮助你编写更为高效和...

    《程序天下:JavaScript实例自学手册》光盘源码

    14.15 在JavaScript运行VBScript函数 14.16 购物篮中常用的计算总价效果 14.17 同一用户的来访统计 14.18 十六进制转换为十进制 14.19 将URL转化为16进制 14.20 小写金额转换为大写 14.21 通过两点坐标计算直线距离 ...

    jQuery响应式宽屏图片3D旋转切换特效.zip

    如:Javascript、VBScript、Document Object Model(DOM,文档对象模型)、Layers和 Cascading Style Sheets(CSS,层叠样式表),这里主要讲Javascript。那么Javascript是什么东西?Javascript就是适应动态网页制作...

    EdoJS,简单、强大的Javascript框架

    代码体积小,内存占用少,运行速度快:卓越的架构和发挥到极致的DOM编程控制,使EdoJS达到浏览器所能的的极限! 超强的列表系列:做到极致的表格Table、树形Tree,甚至TreeGrid等 美观、百搭的界面:只需要编码,...

    d3.js的模块的思维导图

    D3 的运行速度很快,支持大数据集和动态交互以及动画。 不引入新的视觉表示方法,而是借助于现有的 Web 元素: HTML, CSS, SVG 等。例如,可以使用 D3 创建 SVG 元素,并使用外部样式表进行样式化。也可以使用复合...

    Jasmine:JavaScript测试框架-开源

    Jasmine不依赖任何其他JavaScript框架,浏览器或DOM,使其适合于网站,Node.js项目或几乎任何可以运行JavaScript的地方。 Jasmine提供了开箱即用的所有功能来测试您的代码,但是保留了较低的开销,并且绝对没有外部...

    kimbo.js:ECMAScript 5仅兼容JavaScript库

    这个轻量级JavaScript库的目标是使用最新的本机兼容JavaScript和DOM API提供一个美观,简短和扩展的API。 当然,只有在现代浏览器中才能利用惊人的本机速度,而不必再为较旧的浏览器添加额外的字节。 尝试一下,...

Global site tag (gtag.js) - Google Analytics