天天看點

如何優化JavaScript腳本的性能

        随着網絡的發展,網速和機器速度的提高,越來越多的網站用到了豐富用戶端技術。而現在ajax則是最為流行的一種方式。javascript是一種解釋型語言,是以能無法達到和c/java之類的水準,限制了它能在用戶端所做的事情,為了能改進他的性能,我想基于我以前給javascript做過的很多測試來談談自己的經驗,希望能幫助大家改進自己的javascript腳本性能。

語言層次方面

循環

循環是很常用的一個控制結構,大部分東西要依靠它來完成,在javascript中,我們可以使用for(;;),while(),for(in)三種循環,事實上,這三種循環中for(in)的效率極差,因為他需要查詢散列鍵,隻要可以就應該盡量少用。for(;;)和while循環的性能應該說基本(平時使用時)等價。

而事實上,如何使用這兩個循環,則有很大講究。我在測試中有些很有意思的情況,見附錄。最後得出的結論是:

如果是循環變量遞增或遞減,不要單獨對循環變量指派,應該在它最後一次讀取的時候使用嵌套的++或—操作符。

如果要與數組的長度作比較,應該事先把數組的length屬性放入一個局部變量中,減少查詢次數。

局部變量和全局變量

局部變量的速度要比全局變量的通路速度更快,因為全局變量其實是全局對象的成員,而局部變量是放在函數的棧當中的。

不使用eval

使用eval相當于在運作時再次調用解釋引擎對内容進行運作,需要消耗大量時間。這時候使用javascript所支援的閉包可以實作函數模版(關于閉包的内容請參考函數式程式設計的有關内容)

減少對象查找

因為javascript的解釋性,是以a.b.c.d.e,需要進行至少4次查詢操作,先檢查a再檢查a中的b,再檢查b中的c,如此往下。是以如果這樣的表達式重複出現,隻要可能,應該盡量少出現這樣的表達式,可以利用局部變量,把它放入一個臨時的地方進行查詢。

這一點可以和循環結合起來,因為我們常常要根據字元串、數組的長度進行循環,而通常這個長度是不變的,比如每次查詢a.length,就要額外進行一個操作,而預先把var len=a.length,則就少了一次查詢。

字元串連接配接

如果是追加字元串,最好使用s+=anotherstr操作,而不是要使用s=s+anotherstr。

如果要連接配接多個字元串,應該少使用+=,如

s+=a;s+=b;s+=c;應該寫成

s+=a + b + c;而如果是收集字元串,比如多次對同一個字元串進行+=操作的話,最好使用一個緩存。怎麼用呢?使用javascript數組來收集,最後使用join方法連接配接起來,如下

var buf = new array();for(var i = 0; i < 100; i++){ buf.push(i.tostring());}var all = buf.join("");類型轉換

類型轉換是大家常犯的錯誤,因為javascript是動态類型語言,你不能指定變量的類型。

1. 把數字轉換成字元串,應用"" + 1,雖然看起來比較醜一點,但事實上這個效率是最高的,性能上來說:

("" +) > string() > .tostring() > new string()

這條其實和下面的“直接量”有點類似,盡量使用編譯時就能使用的内部操作要比運作時使用的使用者操作要快。

string()屬于内部函數,是以速度很快,而.tostring()要查詢原型中的函數,是以速度遜色一些,new string()用于傳回一個精确的副本。

2. 浮點數轉換成整型,這個更容易出錯,很多人喜歡使用parseint(),其實parseint()是用于将字元串轉換成數字,而不是浮點數和整型之間的轉換,我們應該使用math.floor()或者math.round()。

另外,和第二節的對象查找中的問題不一樣,math是内部對象,是以math.floor()其實并沒有多少查詢方法和調用的時間,速度是最快的。

3. 對于自定義的對象,如果定義了tostring()方法來進行類型轉換的話,推薦顯式調用tostring(),因為内部的操作在嘗試所有可能性之後,會嘗試對象的tostring()方法嘗試能否轉化為string,是以直接調用這個方法效率會更高

使用直接量

其實這個影響倒比較小,可以忽略。什麼叫使用直接量,比如,javascript支援使用[param,param,param,...]來直接表達一個數組,以往我們都使用new array(param,param,...),使用前者是引擎直接解釋的,後者要調用一個array内部構造器,是以要略微快一點點。

同樣,var foo = {}的方式也比var foo = new object();快,var reg = /../;要比var reg=new regexp()快。

字元串周遊操作

對字元串進行循環操作,譬如替換、查找,應使用正規表達式,因為本身javascript的循環速度就比較慢,而正規表達式的操作是用c寫成的語言的api,性能很好。

進階對象

自定義進階對象和date、regexp對象在構造時都會消耗大量時間。如果可以複用,應采用緩存的方式。

dom相關

插入html

很多人喜歡在javascript中使用document.write來給頁面生成内容。事實上這樣的效率較低,如果需要直接插入html,可以找一個容器元素,比如指定一個div或者span,并設定他們的innerhtml來将自己的html代碼插入到頁面中。

對象查詢

使用[“”]查詢要比.items()更快,這和前面的減少對象查找的思路是一樣的,調用.items()增加了一次查詢和函數的調用。

建立dom節點

通常我們可能會使用字元串直接寫html來建立節點,其實這樣做

無法保證代碼的有效性

字元串操作效率低

是以應該是用document.createelement()方法,而如果文檔中存在現成的樣闆節點,應該是用clonenode()方法,因為使用createelement()方法之後,你需要設定多次元素的屬性,使用clonenode()則可以減少屬性的設定次數——同樣如果需要建立很多元素,應該先準備一個樣闆節點。

定時器

如果針對的是不斷運作的代碼,不應該使用settimeout,而應該是用setinterval。settimeout每次要重新設定一個定時器。

其他

腳本引擎

據我測試microsoft的jscript的效率較mozilla的spidermonkey要差很多,無論是執行速度還是記憶體管理上,因為jscript現在基本也不更新了。但spidermonkey不能使用activexobject

檔案優化

檔案優化也是一個很有效的手段,删除所有的空格和注釋,把代碼放入一行内,可以加快下載下傳的速度,注意,是下載下傳的速度而不是解析的速度,如果是本地,注釋和空格并不會影響解釋和執行速度。

總結

本文總結了我在javascript程式設計中所找到的提高javascript運作性能的一些方法,其實這些經驗都基于幾條原則:

直接拿手頭現成的東西比較快,如局部變量比全局變量快,直接量比運作時構造對象快等等。

盡可能少地減少執行次數,比如先緩存需要多次查詢的。

盡可能使用語言内置的功能,比如串連結。

盡可能使用系統提供的api,因為這些api是編譯好的二進制代碼,執行效率很高

同時,一些基本的算法上的優化,同樣可以用在javascript中,比如運算結構的調整,這裡就不再贅述了。但是由于javascript是解釋型的,一般不會在運作時對位元組碼進行優化,是以這些優化仍然是很重要的。

當然,其實這裡的一些技巧同樣使用在其他的一些解釋型語言中,大家也可以進行參考。

參考

http://www.umsu.de/jsperf/ 各種浏覽器的測試對比

http://home.earthlink.net/~kendrasg/info/js_opt/

附錄1

由于是以前做過的測試,測試代碼已經不全,我補充了一部分如下:

var print;

if(typeof document != "undefined" ){

    print = function(){

  document.write(arguments[0]);

 }

}else if(typeof wscript != "undefined" ){

        wscript.echo(arguments[0],arguments[1],arguments[2]);

    }

}

function empty(){

function benchmark(f){

    var i = 0;

    var start = (new date()).gettime();

    while(i < pressure){

        f(i++);

    var end = (new date()).gettime();

    wscript.echo(end-start);

/*

i=0

start = (new date()).gettime();

while(i < 60000){

    c = [i,i,i,i,i,i,i,i,i,i];

    i++;

end = (new date()).gettime();

wscript.echo(end-start);

    c = new array(i,i,i,i,i,i,i,i,i,i);

var end = (new date()).gettime();

*/

function interncast(i){

    return "" + i;

function stringcast(i){

    return string(i)

function newstringcast(i){

    return new string(i)

function tostringcast(i){

    return i.tostring();

function parseint(){

    return parseint(j);

function mathfloor(){

    return math.floor(j);

function floor(){

    return floor(j);

var pressure = 50000;

var a  = "";

var floor = math.floor;

j = 123.123;

print("-------------\nstring conversion test");

print("the empty:", benchmark(empty));

print("intern:", benchmark(interncast));

print("string:");

benchmark(stringcast);

print("new string:");

benchmark(newstringcast);

print("tostring:");

benchmark(tostringcast);

print("-------------\nfloat to int conversion test");

print("parseint");

benchmark(parseint);

print("math.floor");

benchmark(mathfloor);

print("floor")

benchmark(floor);

function newobject(){

    return new object();

function internobject(){

    return {};

print("------------\nliteral test");

print("runtime new object", benchmark(newobject));

print("literal object", benchmark(internobject));

附錄2

代碼1:

    for(var i=0;i<100;i++){

        arr[i]=0;

代碼2:

    while(i < 100){

        arr[i++]=0;

代碼3:

        i++;

在firefox下測試這兩段代碼,結果是代碼2優于代碼1和3,而代碼1一般優于代碼3,有時會被代碼3超過;而在ie 6.0下,測試壓力較大的時候(如測試10000次以上)代碼2和3則有時候優于代碼1,有時候就會遠遠落後代碼1,而在測試壓力較小(如5000次),則代碼2>代碼3>代碼1。

代碼4:

    var a;

        a = 0;

代碼5:

    }上面兩段代碼在firefox和ie下測試結果都是性能接近的。

代碼6:

    var i=0;

    while(i<100){

        a=i;

代碼7:

        a=i++;

代碼8:

        a = i;

代碼9:

    for(var i=0;i<100;){

        a = i++;

    }這四段代碼在firefox下6和8的性能接近,7和9的性能接近,而6, 8 < 7, 9;

最後我們來看一下空循環

代碼10:

    for(var i=0;i<100;i++){   }

代碼11:

    var i;

    while(i<100){        i++;    }

最後的測試出現了神奇的結果,firefox下代碼10所花的時間與代碼11所花的大約是24:1。是以它不具備參考價值,于是我沒有放在一開始給大家看。