如果你認(rèn)為引入Array.prototype.forEach及它的朋友會產(chǎn)生dodo那樣的for循環(huán),請?jiā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"
基礎(chǔ)For循環(huán)的結(jié)構(gòu)包括四個部分: for (initialCode; iteratingCondition; repeatingExpression) { repeatingCode } - 所有4個部分都是非必要的。 我們可以用偽代碼的方式總結(jié)整個過程——(函數(shù)調(diào)用部分單純是為了易讀性) initialCode(); while(iteratingCondition()) { repeatingCode(); repeatingExpression(); } 探索它的模式在這一章里,for循環(huán)的用法會從我們熟悉的樣子變得有點(diǎn)古怪。這樣做的意圖是向你展示結(jié)構(gòu)的多變和語句的力量。我并不是為了給你提供最好的練習(xí)范本。 常見的Array模式for (var i=0; i<=arr.length-1; i++) { var member = arr[i]; doSomething(member); } 儲存array長度以提高效率for (var i=0, l=arr.length; i<=l-1; i++) { var member = arr[i]; doSomething(member); } 合并iteratingCondition和repeatingExpressionfor (var i=arr.length; i--;) { var member = arr[i]; doSomething(member); } 這樣做的原理是當(dāng)i為0, 判定條件變?yōu)閒alse,我們會退出循環(huán)。當(dāng)然,只有我們可以使用逆向循環(huán)的時候,以上代碼才可用。 在iteratingCondition中給變量賦值我們可以把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); } 稀疏數(shù)列測試我們可以倒轉(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部分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 在iteratingCondition中藏一個finally子句我們可以使用 邏輯布爾值||運(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部分無變量定義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++ ); 總結(jié)本文只是列舉了幾種傳統(tǒng)for循環(huán)語法的變形。毫無疑問,你還會用其它一些技術(shù),我也很想聽你說說看。我并不是建議你從明天開始就急匆匆地使用所有這些模式;即使你完全不用也沒有關(guān)系!然而,舊瓶裝新酒是加強(qiáng)對一個語言深度了解的好方法,最終也會保證這個語言自身的發(fā)展和成功。 繼續(xù)閱讀ECMA-262, 5th edition |
|