讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議29:準確使用循環體 >

建議29:準確使用循環體

1.選擇正確的循環體

在大多數編程語言中,代碼執行時間多數消耗在循環的執行上。在一系列編程模式中,循環是最常用的模式之一,因此也是提高性能必須關注的地方之一。理解JavaScript中循環對性能的影響至關重要,因為死循環或長時間運行的循環會嚴重影響用戶體驗。JavaScript定義了4種類型的循環:

第一種循環是標準的for循環,與C語言使用同樣的語法:


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

//循環體

}


for循環是最常用的循環結構,它由4部分組成:初始化體、前測條件、後執行體、循環體。當遇到一個for循環時,初始化體首先執行,然後進入前測條件。如果前測條件的計算結果為true,則執行循環體,然後運行後執行體。for循環在封裝上的直接性是開發者喜歡使用它的原因之一。

第二種循環是while循環。while循環是一個簡單的前測循環,由一個前測條件和一個循環體構成:


var i=0;

while(i<10){

//循環體

i++;

}


在執行循環體之前,先對前測條件進行計算。如果計算結果為true,那麼就執行循環體;否則將跳過循環體。任何for循環都可以寫成while循環,反之亦然。

第三種循環是do while循環。do while循環是JavaScript中唯一一種後測試的循環,它包括兩部分:循環體和後測條件。


var i=0;

do{

//循環體

}while(i++<10);


在一個do while循環中,循環體至少運行一次,後測條件決定循環體是否應再次執行。

第四種循環是for in循環。此循環有一個非常特殊的用途:可以枚舉任何對象的命名屬性。


for(var prop in object){

//循環體

}


每次執行循環,屬性都被對像屬性的名字(一個字符串)填充,直到所有的對象屬性遍歷完成才返回。返回的屬性包括對象的實例屬性和對像從原型鏈繼承而來的屬性。

提高循環性能的起點是選用哪種循環。在JavaScript提供的4種循環類型中,只有for in循環執行速度比其他循環明顯要慢。

由於每次迭代操作要搜索實例或原型的屬性,for in循環每次迭代都要付出更多開銷,因此它比其他類型循環執行速度慢一些。在同樣的循環迭代操作中,其他類型循環執行速度比for in循環快7倍之多,因此推薦這樣做:除非需要對數目不詳的對象屬性進行操作,否則避免使用for in循環。例如,迭代遍歷一個有限的、已知的屬性列表,使用其他循環類型更快,具體的使用模式如下:


var props=[\"prop1\",\"prop2\"],

i=0;

while(i<props.length){

process(object[props[i]]);

}


此代碼創建一個由成員和屬性名構成的隊列。while循環用於遍歷這幾個屬性並處理所對應的對象成員,而不是遍歷對象的每個屬性。此代碼只關注感興趣的屬性,節約了循環時間。

2.比較for和while

除for in循環外,其他循環類型的性能相當,難以確定哪種循環執行速度更快。選擇循環類型應基於需求而不是性能。

可以通過設計for和while循環來完成特定動作的重複性操作。下面分別從語義性、思維模式、達成目標這3個角度來分析如何正確選用while和for循環。

(1)從語義性角度比較

for和while循環可以按如下模式進行相互轉換:


for(initialization;test;increment)//聲明並初始化循環變量、循環條件、遞增循環變量

statements//可執行的循環語句


相當於:


initialization;//聲明並初始化循環變量

while(test){//循環條件

statement//可執行的循環語句

increment;//遞增循環變量

}


for循環是以循環變量的變化來控制循環進程的,即for循環的整個循環流程是預先計劃好的,雖然中途可能會因存在異常或特別情況而退出循環,但是循環的規律性是有章可循的。這樣我們能夠很容易地預知循環的次數、每次循環的狀態等信息。

while循環根據特定條件來決定循環操作,由於這個條件是動態的,無法預知條件何時為true或false,因此該循環的循環操作就具有很大的不確定性,每一次循環時都不知道下一次循環的狀態如何,只能通過條件的動態變化來確定。

因此,for結構常常被用於有規律的重複操作中,如對數組、對像、集合等的操作。當然,對於這些對象的迭代操作,更適合使用for in這種特殊的for循環來操作,因為它提供了更大的便利,可以防止錯誤的發生。while循環更適合用於待定條件的重複操作,以及依據特定事件控制的循環等操作。

(2)從思維模式角度比較

for循環和while循環在思維模式上也存在差異。在for循環中,將循環的三要素(起始值、終止值和步長)定義為3個基本表達式作為結構語法的一部分固定在for語句內,使用小括號進行語法分隔,這與while循環中while語句內僅是條件檢測的表達式截然不同,這樣更有利於JavaScript進行快速預編譯。

因此,當閱讀到for結構的第一行代碼時,就能夠獲取整個循環結構的控制方式,然後再根據上面的表達式來決定是否執行下面的循環體內的語句。可以這樣概括,for結構適合簡單的數值迭代操作。例如,快速閱讀下面示例的代碼:


for(var n=1;n<10;n++){//循環操作的環境條件

alert(n);//循環操作的語句

}


之後可以按以下方式對邏輯思維進行總結。

執行循環條件:1<n<10、步長為n++。

執行循環語句:alert(n)。

這種把循環操作的環境條件和循環操作語句分離開的設計模式能夠提高程序的執行效率,同時也避免了因為把循環條件與循環語句混在一起而造成的遺漏或錯誤。描述這種思維模式的簡化示意圖如圖1.3所示。

圖 1.3 for結構的數值迭代計算

如果for循環的循環條件比較複雜,不是簡單的數值迭代,這時for語句就必須考慮如何把循環條件和循環語句聯繫起來才可以正確地執行整個for循環。因此,根據for結構的運算順序,for語句首先計算第一個和第二個表達式,然後執行循環體語句,最後返回執行for語句的第三個表達式,如此循環執行。例如:


for(var a=true,b=1;a;b++){

if(b>9)//在循環體內間接計算迭代的步長

a=false;

alert(b);

}


在上面的這個示例中,for語句的第三個表達式不是直接計算步長的,整個for循環也沒有明確告知循環步長的表達式,如果要確定迭代的步長,就必須依據循環體內的語句。因此,整個for結構的邏輯思維就存在一個迴旋的過程,如圖1.4所示。

圖 1.4 for結構的條件迭代計算

for循環的特異性導致在執行複雜條件時效率會大大降低。相對而言,while循環天生就是為複雜的條件而設計的,它將複雜的循環控制放在循環體內執行,而while語句自身僅用於測試循環條件,這樣就避免了結構的分隔和邏輯的跳躍。例如,使用while結構來表示這種複雜的條件循環的代碼如下,這種思維變化的示意圖如圖1.5所示。


var a=true,b=1;while(a){//在循環體內間接計算迭代

if(b>9)

a=false;

alert(b);

b++;

}


圖 1.5 while結構的條件計算

(3)從達成目標的角度比較

有些循環的循環次數在循環之前就可以預測,如計算1~100的數字和。而有些循環具有不可預測性,無法事先確定循環的次數,甚至無法預知循環操作的趨向,這些構成了在設計循環結構時必須考慮的達成目標需要解決的問題。即使是相同的操作,如果達成目標的角度不同,可能重複操作的設計也就不同。例如,統計全班學生的成績和統計合格學生的成績就是兩個不同的達成目標。一般來說,在循環結構中動態改變循環變量的值時建議使用while結構,而對於靜態的循環變量,則可以考慮使用for結構。