很早之前读到的一篇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的错误,而在开发环境你怎样都重现不了。解决这个问题的方案之一就是用Ajax加载JS文件,因为Ajax的异步是可控的,所以完全可以等到加载完一个以后再加载第二个,对加载返回的字节流则可以调用eval来解释执行。 - Ext.Ajax.request({
- method: 'GET',
- url: scriptUrl,
- disableCaching: false,
- success: function(response) {
- var jsScript = response.responseText;
- if (jsScript && jsScript.length > 0) {
- if(window.execScript) {
- window.execScript(jsScript);
- } else {
- window.eval(jsScript);
- }
- }
- ScriptLoader.load();
- },
- fail: function() {
- ScriptLoader.load();
- }
- });
引用
Switch Blocks are Linear Evaluation Tables
这个问题,我想只要switch不是在for循环体中不改是最好的。因为毕竟switch的语法要简洁易读的多。在代码的可读性和性能之间有时也需要作出取舍。 引用
Avoid Closures if Possible
闭包是JS中最有争议的一种技术,因为用得好可以举重若轻的解决很多问题,但用的不好会影响性能,降低代码可读性,最糟糕的还是很多内存泄漏问题的罪魁祸首。 一般来说闭包有三种用途,1.匿名自执行函数,2.缓存,3.封装。 关于闭包的作用百度文库有一篇很好的文章: http://wenku.baidu.com/view/ada910d4b14e852458fb577e.html 实际项目中闭包的作用还不只这些,完全取决于程序员的想象力,可以用闭包来改变回调函数的参数,如ExtJs的createDelegate,也可以用来创造一个JQuery式的万能对象。 引用
Don’t use Property Accessor Functions
Property Accessor就是常见的getter和setter,JS中一般和闭包结合使用实现前面提到的数据封装。但是显然没有谁会为所有的property设一对getter和setter就像在JavaBean里那样。如果这有人这么做,那只能说Java中毒太深。或者委婉的说思想还不够JavaScript。