很早之前讀到的一篇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。