高性能javascript笔记:浏览器中DOM操作的性能优化(一)
频繁地对于DOM进行操作的很是损耗性能,但在富网页应用中我们编写脚本无可避免地要跟DOM打交道,到底怎么才能优化这个性能瓶颈呢,大致从以下三种情况去考虑:
- 访问和修改DOM元素
- 修改DOM样式,会造成页面的重绘和重新排版
- 通过DOM事件处理程序来响应用户
访问和修改DOM元素
在浏览器中,DOM的实现和Javascript的实现通常是保持相互独立的。下面了解一下主流浏览器的渲染引擎和JS引擎:
浏览器 | 渲染引擎(内核) | JS引擎 |
---|---|---|
IE | mshtml.dll(Trident) | JScript |
Chrome | WebCore(WebKit) | V8 |
FireFox | Gecko | Spider-Monkey/TraceMonkey |
Safari | WebCore(WebKit) | JavaScriptCore/SquirrelFish |
为什么访问DOM对性能有影响?那是因为两个独立的部分通过他们各自的接口来连接就会带来性能损耗。打个比喻,把DOM看成一个岛屿,把Javascript看成另外一个岛屿,两者之间以一座要收费的桥连接,每次Javascript访问DOM都需要过桥,交一次桥费。来回多了费用自然就高了。所以我们得想方设法减少过桥的次数。
访问DOM元素的代价就是交一次“桥费”,修改DOM元素则会导致浏览器重新计算页面的几何变化。如果是循环修改DOM元素,其代价可想而知。如下代码:
function innerHTMLLoop1(){ for(var count=0;count<10000;count++){ document.getElementById("test").innerHTML +="增加内容"; } }
在这段代码中,每循环一次都要对DOM元素访问两次:一次是读取innerHTML属性的内容,另一次是把新的内容写入它。所以一个优化的办法就是使用一个局部变量存储更新后的内容,在循环结束时再一次性写入:
function innerHTMLLoop2(){
var content =""; for(var count=0;count<10000;count++){ content +="增加内容"; } document.getElementById("test").innerHTML += content ; }
很明显,innerHTMLLoop2交的“桥费”明显要少,因为它只访问了两次DOM元素,一次读入,一次写入。所以一般的法则是:尽量在自己的范围内(Javascript岛屿)活动,别经常到外面鬼混去。(据说,在广州,月薪不到5K的别交女朋友,你懂的!)。
更新页面的两种方法性能比较:innerHTML和DOM方法(如document.creatElment)。《高性能Javascript编程》的答案是:性能差别不大,innerHTML好一些,使用简单嘛。另外还有一个更新页面的方法是节点克隆--element.cloneNode()。
HTML集合的操作
HTML集合是用于存放DOM节点引用的类数组对象。可通过下列的方法或属性得到这样的集合:
- document.getElementsByName()
- document.getElementsByTagName()
- document.getElementsByClassName()
- document.images 返回对文档中所有 Image 对象引用
- document.links 返回对文档中所有 Area 和 Link 对象引用
- document.forms 返回对文档中所有 Form 对象引用
- document.forms[0].elements 返回对文档中第一个表单的所有元素
HTML集合会实时查询文档信息,也就是说当你要用到这个集合时,它会自动查询文档的最新信息。请看如下代码:
1 var allDivs = document.getElementsByTagName("div"); 2 for(var i=0;i<allDivs.length;i++){ 3 document.body.appendChild(document.createElement("div")); 4 }
例如像上面的那段代码其实是个死循环,因为每一次访问div集合的length属性,它都会重新计算文档中的div元素数目。这就是html集合低效率的来源。要改进代码就用一个局部变量保存div集合的length属性: for(var i=0, len=allDivs.length; i<len; i++){...}
访问HTML集合的length比数组的length要慢,所以要访问这种集合类的数目length,我们都应该先用一个局部变量去保存它:var len = 集合.length;
另外,访问数组的元素要比访问HTML集合的元素要快。所以我们可以先把HTML集合转换成数组才去进行相应的操作:
//HTML集合转换成数组 function toArray(coll){ for(var a=[], i=0, len=coll.legnth; i<len; i++){ a[i] = coll[i]; } return a; } //使用 var coll = document.getElementsByTagName("div"); var divs = toArray(coll);
有人可能会问,这样多用了一个数组副本到底值不值得?这个倒是要看情况吧。不过另外一种选择,使用局部变量:
function loopColletion(){ var coll = document.getElmentsByTagName("div"), len = coll.length, el = null; for(var i = 0; i<len; i++){ el = coll[i]; //然后访问局部变量el } }
许多浏览器提供了API函数返回元素节点,这些API都是原生的,所以可用的话就尽量用。下图列举了一些DOM的属性:
上图列举的所有属性能被FF,safari,chrome,opera所支持,ie6-8只支持children。
遍历children比childNodes更快,因为集合项少了。HTML源码中的空格实际上是文本节点,但他们不包含在children中。
另外还有两个比较好的选择器API:document.querySelectorAll()和document.querySelector()。前者接收一个CSS选择器字符串参数并返回一个NodeList类数组对象而不是返回HTML集合,后者只返回符合查询条件的第一个节点。很遗憾IE6、7不支持这两个API。
PS:写一篇博文不容易啊,花了好几个小时,不过可以巩固一下知识,这是一个好习惯。这个博客就是我的学习笔记,O(∩_∩)O哈哈~