1、優先使用陣列而不是Object型別來表示有順序的集合
ECMAScript標準並沒有規定JavaScript的Object類型中的屬性的儲存順序。
但是在使用for..in迴圈對Object中的屬性進行遍歷的時候,確實是需要依賴某種順序的。正因為ECMAScript沒有對這個順序進行明確地規範,所以每個JavaScript執行引擎都能夠根據自身的特點進行實現,那麼在不同的執行環境中就不能保證for..in循環的行為一致性了。
例如,以下程式碼在呼叫report方法時的結果就是不確定的:
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 }]); // ?
如果你確實需要保證運行的結果是建立在數據的順序上,優先使用數組類型來表示數據,而不是直接使用Object類型。同時,也盡量避免使用for..in循環,而使用顯式的for循環:
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"
另一個特別依賴順序的行為是浮點數的計算:
var ratings = { "Good Will Hunting": 0.8, "Mystic River": 0.7, "21": 0.6, "Doubt": 0.9 };
在Item 2中,談到了浮點數的加法運算甚至無法滿足交換律:
(0.1 0.2) 0.3 的結果和 0.1 (0.2 0.3)的結果分別是
0.600000000000001 和 0.6
所以對於浮點數的算術操作,更不能使用任意的順序了:
var total = 0, count = 0; for (var key in ratings) { // unpredictable order total += ratings[key]; count++; } total /= count; total; // ?
當for..in的遍歷順序不一樣時,最後得到的total結果也就不一樣了,以下是兩種計算順序和其對應的結果:
(0.8 + 0.7 + 0.6 +0.9) / 4 // 0.75 (0.6 + 0.8 + 0.7 +0.9) / 4 // 0.7499999999999999
當然,對於浮點數的計算這一類問題,有一個解決方案是使用整數數來表示,例如我們將上面的浮點數首先放大10倍變成整數數據,然後計算結束之後再縮小10倍:
(8+ 7 + 6 + 9) / 4 / 10 // 0.75 (6+ 8 + 7 + 9) / 4 / 10 // 0.75
2、絕對不要在Object.prototype中加入可列舉的(Enumerable)屬性
如果你的程式碼中依賴for..in迴圈來遍歷Object類型中的屬性的話,不要在Object.prototype中加入任何可列舉的屬性。
但是在對JavaScript執行環境進行增強的時候,往往都需要在Object.prototype物件上新增的屬性或方法。例如可以新增一個方法用來得到某個物件中的所有的屬性名稱:
Object.prototype.allKeys = function() { var result = []; for (var key in this) { result.push(key); } return result; };
但是結果是下面這個樣子的:
({ a: 1, b: 2, c: 3}).allKeys(); // ["allKeys", "a", "b","c"]
一個可行的解決方案是使用函數而不是在Object.prototype上定義新的方法:
function allKeys(obj) { var result = []; for (var key in obj) { result.push(key); } return result; }
但是如果你確實需要在Object.prototype上新增新的屬性,同時也不希望該屬性在for..in循環中被遍歷到,那麼可以利用ES5環境提供的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 });
以上程式碼的關鍵部分就是將enumerable屬性設定為false。這樣的話,在for..in迴圈中就無法遍歷該屬性了。
3、陣列遍歷,優先使用for循環,而不是for..in循環
雖然上個Item已經說過這個問題,但對於下面這段程式碼,能看出最後的平均數是多少嗎?
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; // ?
透過計算,最後的結果應該是88。
但不要忘了在for..in迴圈中,被遍歷的永遠是key,而不是value,對於陣列也是如此。因此上述for..in循環中的score並不是期望的98, 74等一系列值,而是0, 1等一系列索引。
所以你或許會認為最後的結果是:
(0 1 … 6) / 7 = 21
但是這個答案也是錯的。另一個關鍵點在於,for..in循環中key的類型永遠都是字串類型,因此這裡的 運算子執行的實際上是字串的拼接操作:
最後得到的total其實是字串00123456。這個字串轉換成數值類型後的值是123456,然後再將它除以元素的個數7,就得到了最後的結果:17636.571428571428
所以,對於陣列遍歷,還是使用標準的for迴圈最好
4、優先使用遍歷方法而非循環
使用循環的時候,很容易違反DRY(Don't Repeat Yourself)原則。這是因為我們通常會選擇複製貼上的方法來避免手寫一段段落的循環語句。但是這樣做回讓程式碼中出現大量重複程式碼,開發人員也在沒有意義地」重複造輪子」。更重要的是,複製貼上的時候很容易忽略循環中的那些細節,例如起始索引值,終止判斷條件等。
例如以下的for迴圈就存在這個問題,假設n是集合物件的長度:
for (var i = 0; i <= n; i++) { ... } // 终止条件错误,应该是i < n for (var i = 1; i < n; i++) { ... } // 起始变量错误,应该是i = 0 for (var i = n; i >= 0; i--) { ... } // 起始变量错误,应该是i = n - 1 for (var i = n - 1; i > 0; i--) { ... } // 终止条件错误,应该是i >= 0
可见在循环的一些细节处理上很容易出错。而利用JavaScript提供的闭包(参见Item 11),可以将循环的细节给封装起来供重用。实际上,ES5就提供了一些方法来处理这一问题。其中的Array.prototype.forEach是最简单的一个。利用它,我们可以将循环这样写:
// 使用for循环 for (var i = 0, n = players.length; i < n; i++) { players[i].score++; } // 使用forEach players.forEach(function(p) { p.score++; });
除了对集合对象进行遍历之外,另一种常见的模式是对原集合中的每个元素进行某种操作,然后得到一个新的集合,我们也可以利用forEach方法实现如下:
// 使用for循环 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()); });
但是由于这种由将一个集合转换为另一个集合的模式十分常见,ES5也提供了Array.prototype.map方法用来让代码更加简单和优雅:
var trimmed = input.map(function(s) { return s.trim(); });
另外,还有一种常见模式是对集合根据某种条件进行过滤,然后得到一个原集合的子集。ES5中提供了Array.prototype.filter来实现这一模式。该方法接受一个Predicate作为参数,它是一个返回true或者false的函数:返回true意味着该元素会被保留在新的集合中;返回false则意味着该元素不会出现在新集合中。比如,我们使用以下代码来对商品的价格进行过滤,仅保留价格在[min, max]区间的商品:
listings.filter(function(listing) { return listing.price >= min && listing.price <= max; });
当然,以上的方法是在支持ES5的环境中可用的。在其它环境中,我们有两种选择: 1. 使用第三方库,如underscore或者lodash,它们都提供了相当多的通用方法来操作对象和集合。 2. 根据需要自行定义。
比如,定义如下的方法来根据某个条件取得集合中前面的若干元素:
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对象上,具体的影响可以参考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]
只有一个场合使用循环会比使用遍历函数要好:需要使用break和continue的时候。 比如,当使用forEach来实现上面的takeWhile方法时就会有问题,在不满足predicate的时候应该如何实现呢?
function takeWhile(a, pred) { var result = []; a.forEach(function(x, i) { if (!pred(x)) { // ? } result[i] = x; }); return result; }
我们可以使用一个内部的异常来进行判断,但是它同样有些笨拙和低效:
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之后,代码甚至比使用它之前更加冗长。这显然是存在问题的。 对于这个问题,ES5提供了some和every方法用来处理存在提前终止的循环,它们的用法如下所示:
[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
这两个方法都是短路方法(Short-circuiting):只要有任何一个元素在some方法的predicate中返回true,那么some就会返回;只有有任何一个元素在every方法的predicate中返回false,那么every方法也会返回false。
因此,takeWhile就可以实现如下:
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; }
实际上,这就是函数式编程的思想。在函数式编程中,你很少能够看见显式的for循环或者while循环。循环的细节都被很好地封装起来了。
5、总结
- 在使用for..in循环时,不要依赖于遍历的顺序。
- 当使用Object类型来保存数据时,需要保证其中的数据是无序的。
- 当需要表示带有顺序的集合时,使用数组类型而不是Object类型。
- 避免向Object.prototype中添加任何属性。
- 如果确实有必要向Object.prototype中添加方法属性,可以考虑使用独立函数替代。
- 使用Object.defineProperty来添加可以不被for..in循环遍历到的属性。
- 当遍历数组时,使用标准的for循环,而不要使用for..in循环。
- 在必要的场合考虑预先保存数组的长度,以提高性能。
- 使用遍历方法Array.prototype.forEach和Array.prototype.map来代替循环,从而让代码更加清晰可读。
- 对于重复出现的循环,可以考虑将它们进行抽象。通过第三方提供的方法或者自己实现。
- 显式的循环在一些场合下还是有用武之地的,相应的也可以使用some或者every方法。
以上就是本文的全部内容,希望通过这篇文章大家更加了解javascript循环的原理,大家共同进步。

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

PhpStorm Mac 版本
最新(2018.2.1 )專業的PHP整合開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中