跟我學(xué)習(xí)javascript的循環(huán)
1、優(yōu)先使用數(shù)組而不是Object類型來表示有順序的集合
ECMAScript標(biāo)準(zhǔn)并沒有規(guī)定對(duì)JavaScript的Object類型中的屬性的存儲(chǔ)順序。
但是在使用for..in循環(huán)對(duì)Object中的屬性進(jìn)行遍歷的時(shí)候,確實(shí)是需要依賴于某種順序的。正因?yàn)镋CMAScript沒有對(duì)這個(gè)順序進(jìn)行明確地規(guī)范,所以每個(gè)JavaScript執(zhí)行引擎都能夠根據(jù)自身的特點(diǎn)進(jìn)行實(shí)現(xiàn),那么在不同的執(zhí)行環(huán)境中就不能保證for..in循環(huán)的行為一致性了。
比如,以下代碼在調(diào)用report方法時(shí)的結(jié)果就是不確定的:
function report(highScores) { var result = ""; var i = 1; for (var name in highScores) { // unpredictable order result += i + ". " + name + ": " + highScores[name] + "\n"; i++; } return result; } report([{ name: "Hank", points: 1110100 }, { name: "Steve", points: 1064500 }, { name: "Billy", points: 1050200 }]); // ?
如果你確實(shí)需要保證運(yùn)行的結(jié)果是建立在數(shù)據(jù)的順序上,優(yōu)先使用數(shù)組類型來表示數(shù)據(jù),而不是直接使用Object類型。同時(shí),也盡量避免使用for..in循環(huán),而使用顯式的for循環(huán):
function report(highScores) { var result = ""; for (var i = 0, n = highScores.length; i < n; i++) { var score = highScores[i]; result += (i + 1) + ". " + score.name + ": " + score.points + "\n"; } return result; } report([{ name: "Hank", points: 1110100 }, { name: "Steve", points: 1064500 }, { name: "Billy", points: 1050200 }]); // "1. Hank: 1110100 2. Steve: 1064500 3. Billy: 1050200\n"
另一個(gè)特別依賴于順序的行為是浮點(diǎn)數(shù)的計(jì)算:
var ratings = { "Good Will Hunting": 0.8, "Mystic River": 0.7, "21": 0.6, "Doubt": 0.9 };
在Item 2中,談到了浮點(diǎn)數(shù)的加法操作甚至不能滿足交換律:
(0.1 + 0.2) + 0.3 的結(jié)果和 0.1 + (0.2 + 0.3)的結(jié)果分別是
0.600000000000001 和 0.6
所以對(duì)于浮點(diǎn)數(shù)的算術(shù)操作,更加不能使用任意的順序了:
var total = 0, count = 0; for (var key in ratings) { // unpredictable order total += ratings[key]; count++; } total /= count; total; // ?
當(dāng)for..in的遍歷順序不一樣時(shí),最后得到的total結(jié)果也就不一樣了,以下是兩種計(jì)算順序和其對(duì)應(yīng)的結(jié)果:
(0.8 + 0.7 + 0.6 +0.9) / 4 // 0.75 (0.6 + 0.8 + 0.7 +0.9) / 4 // 0.7499999999999999
當(dāng)然,對(duì)于浮點(diǎn)數(shù)的計(jì)算這一類問題,有一個(gè)解決方案是使用整型數(shù)來表示,比如我們將上面的浮點(diǎn)數(shù)首先放大10倍變成整型數(shù)據(jù),然后計(jì)算結(jié)束之后再縮小10倍:
(8+ 7 + 6 + 9) / 4 / 10 // 0.75 (6+ 8 + 7 + 9) / 4 / 10 // 0.75
2、絕不要向Object.prototype中添加可列舉的(Enumerable)屬性
如果你的代碼中依賴于for..in循環(huán)來遍歷Object類型中的屬性的話,不要向Object.prototype中添加任何可列舉的屬性。
但是在對(duì)JavaScript執(zhí)行環(huán)境進(jìn)行增強(qiáng)的時(shí)候,往往都需要向Object.prototype對(duì)象添加新的屬性或者方法。比如可以添加一個(gè)方法用于得到某個(gè)對(duì)象中的所有的屬性名:
Object.prototype.allKeys = function() { var result = []; for (var key in this) { result.push(key); } return result; };
但是結(jié)果是下面這個(gè)樣子的:
({ a: 1, b: 2, c: 3}).allKeys(); // ["allKeys", "a", "b","c"]
一個(gè)可行的解決方案是使用函數(shù)而不是在Object.prototype上定義新的方法:
function allKeys(obj) { var result = []; for (var key in obj) { result.push(key); } return result; }
但是如果你確實(shí)需要向Object.prototype上添加新的屬性,同時(shí)也不希望該屬性在for..in循環(huán)中被遍歷到,那么可以利用ES5環(huán)境提供的Object.defineProject方法:
Object.defineProperty(Object.prototype, "allKeys", { value: function() { var result = []; for (var key in this) { result.push(key); } return result; }, writable: true, enumerable: false, configurable: true });
以上代碼的關(guān)鍵部分就是將enumerable屬性設(shè)置為false。這樣的話,在for..in循環(huán)中就無法遍歷該屬性了。
3、對(duì)于數(shù)組遍歷,優(yōu)先使用for循環(huán),而不是for..in循環(huán)
雖然上個(gè)Item已經(jīng)說過這個(gè)問題,但是對(duì)于下面這段代碼,能看出最后的平均數(shù)是多少嗎?
var scores = [98, 74, 85, 77, 93, 100, 89]; var total = 0; for (var score in scores) { total += score; } var mean = total / scores.length; mean; // ?
通過計(jì)算,最后的結(jié)果應(yīng)該是88。
但是不要忘了在for..in循環(huán)中,被遍歷的永遠(yuǎn)是key,而不是value,對(duì)于數(shù)組同樣如此。因此上述for..in循環(huán)中的score并不是期望的98, 74等一系列值,而是0, 1等一系列索引。
所以你也許會(huì)認(rèn)為最后的結(jié)果是:
(0 + 1+ …+ 6) / 7 = 21
但是這個(gè)答案也是錯(cuò)的。另外一個(gè)關(guān)鍵點(diǎn)在于,for..in循環(huán)中key的類型永遠(yuǎn)都是字符串類型,因此這里的+操作符執(zhí)行的實(shí)際上是字符串的拼接操作:
最后得到的total實(shí)際上是字符串00123456。這個(gè)字符串轉(zhuǎn)換成數(shù)值類型后的值是123456,然后再將它除以元素的個(gè)數(shù)7,就得到了最后的結(jié)果:17636.571428571428
所以,對(duì)于數(shù)組遍歷,還是使用標(biāo)準(zhǔn)的for循環(huán)最好
4、優(yōu)先使用遍歷方法而非循環(huán)
在使用循環(huán)的時(shí)候,很容易違反DRY(Don't Repeat Yourself)原則。這是因?yàn)槲覀兺ǔ?huì)選擇復(fù)制粘貼的方法來避免手寫一段段的循環(huán)語句。但是這樣做回讓代碼中出現(xiàn)大量重復(fù)代碼,開發(fā)人員也在沒有意義地”重復(fù)造輪子”。更重要的是,在復(fù)制粘貼的時(shí)候很容易忽視循環(huán)中的那些細(xì)節(jié),比如起始索引值,終止判斷條件等。
比如以下的for循環(huán)就存在這個(gè)問題,假設(shè)n是集合對(duì)象的長(zhǎng)度:
for (var i = 0; i <= n; i++) { ... } // 終止條件錯(cuò)誤,應(yīng)該是i < n for (var i = 1; i < n; i++) { ... } // 起始變量錯(cuò)誤,應(yīng)該是i = 0 for (var i = n; i >= 0; i--) { ... } // 起始變量錯(cuò)誤,應(yīng)該是i = n - 1 for (var i = n - 1; i > 0; i--) { ... } // 終止條件錯(cuò)誤,應(yīng)該是i >= 0
可見在循環(huán)的一些細(xì)節(jié)處理上很容易出錯(cuò)。而利用JavaScript提供的閉包(參見Item 11),可以將循環(huán)的細(xì)節(jié)給封裝起來供重用。實(shí)際上,ES5就提供了一些方法來處理這一問題。其中的Array.prototype.forEach是最簡(jiǎn)單的一個(gè)。利用它,我們可以將循環(huán)這樣寫:
// 使用for循環(huán) for (var i = 0, n = players.length; i < n; i++) { players[i].score++; } // 使用forEach players.forEach(function(p) { p.score++; });
除了對(duì)集合對(duì)象進(jìn)行遍歷之外,另一種常見的模式是對(duì)原集合中的每個(gè)元素進(jìn)行某種操作,然后得到一個(gè)新的集合,我們也可以利用forEach方法實(shí)現(xiàn)如下:
// 使用for循環(huán) var trimmed = []; for (var i = 0, n = input.length; i < n; i++) { trimmed.push(input[i].trim()); } // 使用forEach var trimmed = []; input.forEach(function(s) { trimmed.push(s.trim()); });
但是由于這種由將一個(gè)集合轉(zhuǎn)換為另一個(gè)集合的模式十分常見,ES5也提供了Array.prototype.map方法用來讓代碼更加簡(jiǎn)單和優(yōu)雅:
var trimmed = input.map(function(s) { return s.trim(); });
另外,還有一種常見模式是對(duì)集合根據(jù)某種條件進(jìn)行過濾,然后得到一個(gè)原集合的子集。ES5中提供了Array.prototype.filter來實(shí)現(xiàn)這一模式。該方法接受一個(gè)Predicate作為參數(shù),它是一個(gè)返回true或者false的函數(shù):返回true意味著該元素會(huì)被保留在新的集合中;返回false則意味著該元素不會(huì)出現(xiàn)在新集合中。比如,我們使用以下代碼來對(duì)商品的價(jià)格進(jìn)行過濾,僅保留價(jià)格在[min, max]區(qū)間的商品:
listings.filter(function(listing) { return listing.price >= min && listing.price <= max; });
當(dāng)然,以上的方法是在支持ES5的環(huán)境中可用的。在其它環(huán)境中,我們有兩種選擇: 1. 使用第三方庫,如underscore或者lodash,它們都提供了相當(dāng)多的通用方法來操作對(duì)象和集合。 2. 根據(jù)需要自行定義。
比如,定義如下的方法來根據(jù)某個(gè)條件取得集合中前面的若干元素:
function takeWhile(a, pred) { var result = []; for (var i = 0, n = a.length; i < n; i++) { if (!pred(a[i], i)) { break; } result[i] = a[i]; } return result; } var prefix = takeWhile([1, 2, 4, 8, 16, 32], function(n) { return n < 10; }); // [1, 2, 4, 8]
為了更好的重用該方法,我們可以將它定義在Array.prototype對(duì)象上,具體的影響可以參考Item 42。
Array.prototype.takeWhile = function(pred) { var result = []; for (var i = 0, n = this.length; i < n; i++) { if (!pred(this[i], i)) { break; } result[i] = this[i]; } return result; }; var prefix = [1, 2, 4, 8, 16, 32].takeWhile(function(n) { return n < 10; }); // [1, 2, 4, 8]
只有一個(gè)場(chǎng)合使用循環(huán)會(huì)比使用遍歷函數(shù)要好:需要使用break和continue的時(shí)候。 比如,當(dāng)使用forEach來實(shí)現(xiàn)上面的takeWhile方法時(shí)就會(huì)有問題,在不滿足predicate的時(shí)候應(yīng)該如何實(shí)現(xiàn)呢?
function takeWhile(a, pred) { var result = []; a.forEach(function(x, i) { if (!pred(x)) { // ? } result[i] = x; }); return result; }
我們可以使用一個(gè)內(nèi)部的異常來進(jìn)行判斷,但是它同樣有些笨拙和低效:
function takeWhile(a, pred) { var result = []; var earlyExit = {}; // unique value signaling loop break try { a.forEach(function(x, i) { if (!pred(x)) { throw earlyExit; } result[i] = x; }); } catch (e) { if (e !== earlyExit) { // only catch earlyExit throw e; } } return result; }
可是使用forEach之后,代碼甚至比使用它之前更加冗長(zhǎng)。這顯然是存在問題的。 對(duì)于這個(gè)問題,ES5提供了some和every方法用來處理存在提前終止的循環(huán),它們的用法如下所示:
[1, 10, 100].some(function(x) { return x > 5; }); // true [1, 10, 100].some(function(x) { return x < 0; }); // false [1, 2, 3, 4, 5].every(function(x) { return x > 0; }); // true [1, 2, 3, 4, 5].every(function(x) { return x < 3; }); // false
這兩個(gè)方法都是短路方法(Short-circuiting):只要有任何一個(gè)元素在some方法的predicate中返回true,那么some就會(huì)返回;只有有任何一個(gè)元素在every方法的predicate中返回false,那么every方法也會(huì)返回false。
因此,takeWhile就可以實(shí)現(xiàn)如下:
function takeWhile(a, pred) { var result = []; a.every(function(x, i) { if (!pred(x)) { return false; // break } result[i] = x; return true; // continue }); return result; }
實(shí)際上,這就是函數(shù)式編程的思想。在函數(shù)式編程中,你很少能夠看見顯式的for循環(huán)或者while循環(huán)。循環(huán)的細(xì)節(jié)都被很好地封裝起來了。
5、總結(jié)
- 在使用for..in循環(huán)時(shí),不要依賴于遍歷的順序。
- 當(dāng)使用Object類型來保存數(shù)據(jù)時(shí),需要保證其中的數(shù)據(jù)是無序的。
- 當(dāng)需要表示帶有順序的集合時(shí),使用數(shù)組類型而不是Object類型。
- 避免向Object.prototype中添加任何屬性。
- 如果確實(shí)有必要向Object.prototype中添加方法屬性,可以考慮使用獨(dú)立函數(shù)替代。
- 使用Object.defineProperty來添加可以不被for..in循環(huán)遍歷到的屬性。
- 當(dāng)遍歷數(shù)組時(shí),使用標(biāo)準(zhǔn)的for循環(huán),而不要使用for..in循環(huán)。
- 在必要的場(chǎng)合考慮預(yù)先保存數(shù)組的長(zhǎng)度,以提高性能。
- 使用遍歷方法Array.prototype.forEach和Array.prototype.map來代替循環(huán),從而讓代碼更加清晰可讀。
- 對(duì)于重復(fù)出現(xiàn)的循環(huán),可以考慮將它們進(jìn)行抽象。通過第三方提供的方法或者自己實(shí)現(xiàn)。
- 顯式的循環(huán)在一些場(chǎng)合下還是有用武之地的,相應(yīng)的也可以使用some或者every方法。
以上就是本文的全部?jī)?nèi)容,希望通過這篇文章大家更加了解javascript循環(huán)的原理,大家共同進(jìn)步。
- javascript下for循環(huán)用法小結(jié)
- javascript之循環(huán)停頓上下滾動(dòng)
- javascript中利用數(shù)組實(shí)現(xiàn)的循環(huán)隊(duì)列代碼
- javascript 循環(huán)讀取JSON數(shù)據(jù)的代碼
- javascript forEach通用循環(huán)遍歷方法
- javascript 循環(huán)語句 while、do-while、for-in、for用法區(qū)別
- Javascript中的for in循環(huán)和hasOwnProperty結(jié)合使用
- JavaScript的遞歸之遞歸與循環(huán)示例介紹
- JavaScript中for..in循環(huán)陷阱介紹
- javascript中的循環(huán)語句for語句深入理解
- javascript閉包傳參和事件的循環(huán)綁定示例探討
- JavaScript中對(duì)循環(huán)語句的優(yōu)化技巧深入探討
- javascript使用for循環(huán)批量注冊(cè)的事件不能正確獲取索引值的解決方法
- javascript實(shí)現(xiàn)圖片循環(huán)漸顯播放的方法
- 簡(jiǎn)單學(xué)習(xí)JavaScript中的for語句循環(huán)結(jié)構(gòu)
- JavaScript中利用各種循環(huán)進(jìn)行遍歷的方式總結(jié)
- 跟我學(xué)習(xí)javascript的for循環(huán)和for...in循環(huán)
相關(guān)文章
JavaScript返回網(wǎng)頁中超鏈接數(shù)量的方法
這篇文章主要介紹了JavaScript返回網(wǎng)頁中超鏈接數(shù)量的方法,使用javascript中的document.links實(shí)現(xiàn)這一功能,需要的朋友可以參考下2015-04-04小程序?qū)崿F(xiàn)短信登錄倒計(jì)時(shí)
這篇文章主要為大家詳細(xì)介紹了小程序?qū)崿F(xiàn)短信登錄倒計(jì)時(shí),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07js簡(jiǎn)單正則驗(yàn)證漢字英文及下劃線的方法
這篇文章主要介紹了js簡(jiǎn)單正則驗(yàn)證漢字英文及下劃線的方法,結(jié)合完整實(shí)例形式分析了javascript針對(duì)中英文字母與下劃線的正則驗(yàn)證方法,需要的朋友可以參考下2016-11-11JS實(shí)現(xiàn)對(duì)json對(duì)象排序并刪除id相同項(xiàng)功能示例
這篇文章主要介紹了JS實(shí)現(xiàn)對(duì)json對(duì)象排序并刪除id相同項(xiàng)功能,涉及javascript針對(duì)json格式數(shù)據(jù)的遍歷、運(yùn)算、判斷、添加、刪除等相關(guān)操作技巧,需要的朋友可以參考下2018-04-04JavaScript實(shí)現(xiàn)數(shù)組隨機(jī)排序的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)數(shù)組隨機(jī)排序的方法,涉及javascript數(shù)組遍歷與排序的相關(guān)技巧,需要的朋友可以參考下2015-06-06Jquery Autocomplete 結(jié)合asp.net使用要點(diǎn)
Jquery的Autocomplete是一個(gè)很好的智能提示插件,但是在實(shí)際使用過程中還是會(huì)遇到一些小問題.2010-10-10