讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議178:推薦的JavaScript性能調優 >

建議178:推薦的JavaScript性能調優

由於JavaScript語言的單線程和解釋執行的兩個特點決定了它本身有很多地方存在性能問題,因此需要進行性能優化的地方還是很多,下面就JavaScript存在的性能障礙節點進行說明。

(1)eval問題

比較下面兩段代碼:


//方法1

var reference={},props=\"p1\";

eval(\"reference.\"+props+\"=5\")

//方法2

var reference={},props=\"p1\";

reference[props]=5


沒有eval的代碼要比有eval的代碼快100倍以上,這是因為JavaScript代碼在執行前會進行類似預編譯的操作:首先會創建一個當前執行環境下的活動對象,並將那些用var聲明的變量設置為活動對象的屬性,但此時這些變量都是undefined,還會將那些以function定義的函數也添加為活動對象的屬性,而且它們的值正是函數的定義。如果使用了eval,那麼eval中的代碼(實際上為字符串)無法預先識別其上下文,無法被提前解析和優化,即無法進行預編譯的操作,所以,代碼性能也會大幅度降低。

(2)Function用法

比較下面兩段代碼:


//方法1

var func1=new Function(\"return arguments[0]+arguments[1]\");

func1(10,20);

//方法2

var func2=function{

return arguments[0]+arguments[1]

};

func2(10,20);


第一段代碼的效率會比第二段代碼的效率差很多,故推薦使用第二種方式。

(3)字符串拼接

經常看到如下形式的簡單字符串拼接代碼:


str+=\"str1\"+\"str2\"


這是拼接字符串常用的方式,但這種方式會有一些臨時變量的創建和銷毀,使性能受到影響,所以推薦使用如下方式拼接:


var str_array=;

str_array.push(\"str1\");

str_array.push(\"str2\");

str=str_array.join(\"\");


這裡利用數組(array)的join方法實現字符串的拼接,尤其是在早期的IE版本(如IE 6)上運行時,會有非常明顯的性能上的改進。

當然,最新的瀏覽器(如Firefox、IE 8及其以上版本等)對字符串的拼接做了優化,也可以這樣實現字符串快速拼接:


str+=\"str1\"

str+=\"str2\"


新的瀏覽器對「+=」做了優化,其性能略好於數組的join方法。在不久的將來,更新版本的瀏覽器可能對「+」進行優化,那時可以直接寫成:str+=\"str1\"+\"str2\"。

(4)隱式類型轉換

參考如下隱式類型轉換代碼:


var str=\"12345678\",arr=;

for(var i=0;i<=s.length;i++){

arr.push(str.charAt(i));

}


在上面代碼中,每次循環時都會調用字符串的charAt方法,但由於將常量「12345678」賦值給str,因此這裡的str並不是一個字符串對象,每次調用charAt方法時都會臨時構造值為「12345678」的字符串對象,然後調用charAt方法,最後再釋放這個字符串臨時對象。可以通過一些改進來避免隱式類型轉換。


var str=new Stirng(\"12345678\"),arr=;

for(var i=0;i<=s.length;i++){

arr.push(str.charAt(i));

}


這樣,變量str作為一個字符串對象,就不會有這種隱式類型轉換的過程了,效率會顯著提高。

(5)字符串匹配

JavaScript具有RegExp對象,支持對字符串的正則表達式匹配。它是一個很好的工具,但性能並不是非常理想。相反,字符串對像(String)本身的一些基本方法的效率是非常高的,如substring、indexOf、charAt等,在需要用正則表達式匹配字符串時,可以考慮下面的因素。

❑是否能夠通過字符串對像本身支持的基本方法解決問題。

❑是否可以通過substring來縮小需要用正則表達式的範圍。

這些方式都能夠有效地提高程序的運行效率。關於正則表達式對象,還有一點需要注意:


for(var i=0;i<=str_array.length;i++){

if(str_array[i].match(/^s*extras/)){

//...

}

}


在這裡向match方法傳入/^s*extras/是會影響執行效率的,因為在這一過程中會構建臨時值為/^s*extras/的正則表達式對象,執行match方法,然後銷毀臨時的正則表達式對象。可以這樣進行優化:


var sExpr=/^s*extras/;

for(var i=0;i<=str_array.length;i++){

if(str_array[i].match(sExpr)){

//...

}

}


這樣就不會有臨時對象了。

(6)setTimeout和setInterval

setTimeout和setInterval這兩個函數可以接受字符串變量,但會帶來和之前談到的eval類似的性能問題,所以建議還是直接傳入函數對像本身。

(7)利用提前退出

參考如下兩段代碼:


//代碼1

if(source.match(//)){

}

//代碼2

if(name.indexOf&&source.match(//)){

}


代碼2中多了一個對name.indexOf的判斷,這使程序每次執行到這一段時會先執行indexOf的判斷,再執行後面的match,在indexOf比match效率高很多的前提下,這樣做會減少match的執行次數,從而在一定程度上提高效率。