日期:2014-05-16  浏览次数:20338 次

IE下JavaScript性能调优
很早之前读到的一篇IE的 Performance PM Peter Gurevich关于IE下性能优化的好文章,其实大多数的建议也适合其他的浏览器下的优化,很多Tips在Nicholas C. Zakas的《High Performance JavaScript》中也有提到。文章共分为三个部分。
第一部分链接地址:
http://blogs.msdn.com/b/ie/archive/2006/08/28/728654.aspx
第二部分链接地址:
http://blogs.msdn.com/b/ie/archive/2006/11/16/ie-javascript-performance-recommendations-part-2-javascript-code-inefficiencies.aspx
第三部分链接地址:
http://blogs.msdn.com/b/ie/archive/2007/01/04/ie-jscript-performance-recommendations-part-3-javascript-code-inefficiencies.aspx

引用
Symbolic Look-up Recommendations

简单的说就是多用局部变量引用所有查找对象。例如document.forms[0]会先查找document然后在document中查找forms属性,然后在forms里查找第一个元素。如果只用一次当然没有问题,但如果需要反复用到如
check(document.forms[0].elements[0]);
check(document.forms[0].elements[1]);
check(document.forms[0].elements[2]);
...
document.forms[0].submit();

会每次重复查找同样的元素,若使用局部变量引用则变成
var fm = document.forms[0];
var fmEls = fm.elements; 
check(fmEls[0]);
check(fmEls[1]);
check(fmEls[2]);
...
fm.submit();

这样只需要查找一次document.forms[0],之后都是在访问局部变量里的引用。


function BuildUI(){?????
 var baseElement = document.getElementById(‘target’);????? 
 baseElement.innerHTML = ‘’; // Clear out the previous?????           baseElement.innerHTML += BuildTitle();????? 
 baseElement.innerHTML += BuildBody();????? 
  baseElement.innerHTML += BuildFooter();
}

function BuildUI(){????? 
var elementText = BuildTitle() + BuildBody() + BuildFooter();????? document.getElementById(‘target’).innerHTML = elementText;
}  

这个例子有两个主要问题超过了使用局部变量。
第一是问题是反复给innerHTML设值,给元素的innerHTML设置会导致页面元素的重渲染,而渲染页面元素在浏览器中特别是老板的IE中是相当耗时的工作,这个任何了解MVC的人都应该都能理解。
第二个问题是不应该在String上反复的使用+=操作,因为String是不可变的对象,+=会不断的创造很多瞬态对象。在Java中我们用StringBuilder来提高性能,在JavaScript中用Array来达到同样的目的。
function BuildUI(){
var elementText =[];
elementText.push(BuildTitle());
elementText.push(BuildBody());
elementText.push(BuildFooter());
document.getElementById(‘target’).innerHTML = elementText.join('');
}  



另外一个关于局部变量缓存常见的问题是在for循环中,以ExtJs中常见的遍历Store为例。
for(var i =0; i < store.getCount(); i++){
	var myName = store.getAt(i).get('name');
	...
	store.getAt(i).set('name', 'other name');
}

在for循环中i < store.getCount()是每次循环都会调用, 这样每循环一次都要调用store的getCount方法。

大多数时候用不用局部变量都不会有太大的性能差别,一般是<1ms.但是在循环中一定要缓存一切可以缓存的东西,因为性能的一点点损失都会被放大N倍,特别是在嵌套循环中这种放大效果更明显。所以在JS性能调优的时候我会习惯先去找for loop,因为这里才是最值得你调优的地方。
上面的例子里还有一个不太明显的问题,就是变量myName。因为JavaScript里变量是没有块级作用域的,所有myName在for循环外面还是可以访问到的。这样用一个不断被复写的变量比每次循环都创建一个新的外部变量要经济的多。
for(var i=0, ilen=store.getCount(), rec, myName; i < ilen; i++){
	rec = store.getAt(i);
	myName = rec.get('name');
	...
	rec.set('name', 'other name');
}


引用
Avoid Using the ‘with’ Keyword

能改变局部作用域的"with"除了《JavaScript王者归来》里曾经见过,在实际的项目和开源框架中我还真没见谁用过。这种用于炫技与性能无益的技巧我想还是尽量忘掉好了。去粗取精,这也正是《JavaScript:The Good Parts》中Douglas Crockford所倡导的。

引用
Running Code Using the ‘eval’ Statement is Expensive
Requirements of Eval for JSON Expressions

eval is evil, 但是滥用eval的情况还是比较少见的,因为需要用字符串的形式执行JS的情形并不多见,就算要写一些很generic的动态函数一般也能找到其他的替代方法。下面介绍一下eval的两种用途:
1.解析JSON,一般是Ajax请求的返回值,但也可以用来解析JSP标签的输出
var popupMessage = eval('<c:popupMessage/>');


2.用来动态加载外部JS文件.为什么不用script标签呢?这里存在一个普遍性问题,script标签是异步加载的,出于性能考虑不会等第一份文件加载结束再加载第二份。如果两份JS文件中的代码之间有依赖关系,如第二份文件中的代码调用了第一份文件中定义好的函数,在开发环境因为文件加载的速度很快没有问题,但是在公网环境下就不同了,可能第二份文件比较小,也可能是网络传输的延迟,导致第二份文件比第一份文件先加载完成,这样就会得到一个莫名其妙的variable is undefined的错误,而