如果你認(rèn)為引入Array.prototype.forEach及它的朋友會產(chǎn)生dodo那樣的for循環(huán),請再想想。for循環(huán)老當(dāng)益壯。
For循環(huán)經(jīng)常被當(dāng)作一只搗蛋的小馬駒(難以馴服—譯者按)。它最適合于解決經(jīng)典的重復(fù)列舉問題:
for (var i=0; i<=arr.length-1; i++) { //do something to each member}
但隨著更高等級命令函數(shù)的出現(xiàn),無論是在本地程序還是在架構(gòu)中,我們都可以寫以下代碼(或類似變異)
arr.forEach(function(each)) { //do something to each});
諷刺的是,當(dāng)高等級函數(shù)逐漸取代了老舊的傳統(tǒng)模式,我們也終于可以從舊習(xí)慣中解脫出來,去探索一些更有意思的for循環(huán)模式。
開胃菜——這是一個超級簡潔的方法去生成并顯示出斐波那契數(shù)列中的前n位:
for ( var i=2, r=[0,1]; i<15 || alert(r); r.push(r[i-1] + r[i-2]), i++);//alerts "0,1,1,2,3,5,8,13,21,34,55,89,144,233,377"
For循環(huán)的結(jié)構(gòu)包括四個部分:
for (initialCode; iteratingCondition; repeatingExpression) { repeatingCode}
- 所有4個部分都是非必要的。
-initialCode不需要被賦一個變量—任何有效的表達(dá)式都可以。
-iteratingCondition和repeatingExpression不能包含變量定義。
-當(dāng)repeatingCode為空或只有一句,花括號就不是必要的。
當(dāng)repeatingCode部分被執(zhí)行后,repeatingExpression會被再次估值。
我們可以用偽代碼的方式總結(jié)整個過程——(函數(shù)調(diào)用部分單純是為了易讀性)
initialCode();while(iteratingCondition()) { repeatingCode(); repeatingExpression();}
在這一章里,for循環(huán)的用法會從我們熟悉的樣子變得有點(diǎn)古怪。這樣做的意圖是向你展示結(jié)構(gòu)的多變和語句的力量。我并不是為了給你提供最好的練習(xí)范本。
for (var i=0; i<=arr.length-1; i++) { var member = arr[i]; doSomething(member);}
for (var i=0, l=arr.length; i<=l-1; i++) { var member = arr[i]; doSomething(member);}
for (var i=arr.length; i--;) { var member = arr[i]; doSomething(member);}
這樣做的原理是當(dāng)i為0, 判定條件變?yōu)閒alse,我們會退出循環(huán)。當(dāng)然,只有我們可以使用逆向循環(huán)的時候,以上代碼才可用。
我們可以把repeatingCode中的變量賦值移到theriteratingCondition中去。當(dāng)each無定義時,循環(huán)就會被終止。
這樣我們就縮短了代碼的長度并且不需要檢查數(shù)列長度。語法變得更直接,在我看來,也更優(yōu)雅。只有當(dāng)數(shù)列是連續(xù)的,并且不會有出現(xiàn)“falsey”值(null,0,空或false)的時候,這個方法才可用。
for (var i=0, each; each = arr[i]; i++) { doSomething(each);}
我們可以倒轉(zhuǎn)以上的模式來主動地檢查一個稀疏的數(shù)列或列表。這里我們高效地檢測未定義參數(shù):
var func = function(a,b,c) { for (var i=0; arguments[i] !== undefined; i++); var allArguments = (i >= arguments.callee.length); //...}
repeatingCode和repeatingExpression有著相同的用途。所以如果重復(fù)代碼部分可以簡單地總結(jié)為一句代碼,你可以刪掉整個repeatCode部分。
function sum(arr) { for (var i=arr.length, r=0; i--; r += arr[i]); return r;}sum([3,5,0,-2,7,8]); //21
我們可以使用 邏輯布爾值||運(yùn)算符 來定義一個final語句。這個小函數(shù)會把一個數(shù)組中的值相加,并在完成后打印出這個值。
function shoutOutSum(arr, x) { for (var i=arr.length, r=0; i-- || alert(r); r += arr[i]);}shoutOutSum([3,5,0,-2,7,8]); //alerts "21"
當(dāng)然如果你的最終子句不返回falsey值的話,你就有麻煩了。你會陷入死循環(huán)。為保證這不會發(fā)生,你把final變量和false &&上。這會顯得有點(diǎn)笨拙:
function sumAndMultiply(arr, x) { for (var i=arr.length, r=0; i-- || ((r = r*x) && false); r += arr[i]); return r;}sumAndMultiply([3,5,0,-2,7,8], 5); //105
更新:Brendan Eich建議用void來替代:
function sumAndMultiply(arr, x) { for (var i=arr.length, r=0; i-- || void (r = r*x); r += arr[i]); return r;}
initialCode不需要變量定義。為了不因variable hoisting產(chǎn)生混淆,很多程序員在函數(shù)的開頭定義所有變量,有些JavaScript專家(包括Douglas Crockford)甚至不會再for循環(huán)中定義變量。
function myFunction(arr) { var i; //... for (i=0; i < arr.length; i++) { //... } //...}
像我之前所說,你幾乎一定會想用initialCode來給一些變量賦值,但這不是必須的。以下代碼是一個挺爛的for循環(huán),但我只是想用它來證明這一點(diǎn)。
var i = 0;for ( console.log('start:',+new Date); i<1000 || console.log('finish:',+new Date); i++);
ECMA-262, 5th edition
section 12.6.3 (The for statement)
sections 15.4.4.14 to 15.4.4.22 (High OrderArray Functions)