我被告知在JavaScript中不要使用for...in
循环遍历数组。为什么?
我被告知在JavaScript中不要使用for...in
循环遍历数组。为什么?
原因是这个结构:
var a = []; // Create a new empty array.
a[5] = 5; // Perfectly legal JavaScript that resizes the array.
for (var i = 0; i < a.length; i++) {
// Iterate over numeric indexes from 0 to 5, as everyone expects.
console.log(a[i]);
}
/* Will display:
undefined
undefined
undefined
undefined
undefined
5
*/
有时候,它可能与其他事物完全不同:
var a = [];
a[5] = 5;
for (var x in a) {
// Shows only the explicitly set index of "5", and ignores 0-4
console.log(x);
}
/* Will display:
5
*/
还要考虑到JavaScript库可能会执行这样的操作,从而影响你创建的任何数组:
// Somewhere deep in your JavaScript library...
Array.prototype.foo = 1;
// Now you have no idea what the below code will do.
var a = [1, 2, 3, 4, 5];
for (var x in a){
// Now foo is a part of EVERY array and
// will show up here as a value of 'x'.
console.log(x);
}
/* Will display:
0
1
2
3
4
foo
*/
(var x in a)
而不是(x in a)
,以免创建全局变量。请注意保持原意,简化语言但不要添加解释或其他内容。 - Chris Morganfor (var key in object)
。但是,如果你想迭代一个数组的元素,请使用for(var i = 0; i < array.length; i += 1)
。请注意,两种迭代方法的语法略有不同,并且用于不同类型的数据结构。 - Martijnfor-in
语句本身并不是"不良实践",但它可能被误用,例如用于枚举数组或类似数组的对象。
for-in
语句的目的是枚举对象属性。该语句会沿着原型链向上遍历,还会枚举继承的属性,这有时并不是想要的。var array = [];
array[2] = 'c';
array[1] = 'b';
array[0] = 'a';
for (var p in array) {
//... p will be "2", "1" and "0" on IE
}
另外,谈到继承属性,如果你例如扩展了Array.prototype
对象(像一些库如MooTools所做的那样),那些属性也将被枚举:
Array.prototype.last = function () { return this[this.length-1]; };
for (var p in []) { // an empty array
// last will be enumerated
}
如我之前所说,遍历数组或类数组对象最好的方法是使用顺序循环,例如简单的 for
/while
循环。
当您只想枚举对象的自有属性(即那些不是继承来的属性)时,可以使用 hasOwnProperty
方法:
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
// prop is not inherited
}
}
有些人甚至建议直接从Object.prototype
调用该方法,以避免如果有人向我们的对象添加一个名为hasOwnProperty
的属性而出现问题:
for (var prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
// prop is not inherited
}
}
for..in
循环比普通循环慢得多。 - Chris Morganfor..in
不是坏习惯,但它可能会被滥用。你有一个真实世界的好例子吗?在这个例子中,你确实想要遍历对象的所有属性,包括继承的属性? - rjmunrofor (var p in array) { array[p]; }
访问该值。 - equiman不应该使用for..in
来迭代数组元素有三个原因:
for..in
将循环遍历数组对象的所有自身属性和继承属性,但不包括DontEnum
属性。这意味着如果有人向特定的数组对象添加属性(我自己也有这样做的合法原因)或更改了Array.prototype
(这在代码与其他脚本协同工作时被认为是不好的实践),这些属性也将被迭代。通过检查hasOwnProperty()
可以排除继承属性,但这不能帮助您处理在数组对象本身中设置的属性。
for..in
不能保证元素的顺序不变
它很慢,因为您必须遍历数组对象及其整个原型链的所有属性,并且仅会得到属性的名称,即需要进行额外的查找才能获取值
因为for...in枚举的是包含数组的对象,而不是数组本身。如果我向数组的原型链中添加一个函数,那么它也会被包括在内。例如:
Array.prototype.myOwnFunction = function() { alert(this); }
a = new Array();
a[0] = 'foo';
a[1] = 'bar';
for (var x in a) {
document.write(x + ' = ' + a[x]);
}
这将会写入:
0 = foo
1 = bar
myOwnFunction = function() { alert(this); }
既然你永远无法确定原型链上是否会添加任何内容,因此最好使用for循环来枚举数组:
for (var i=0,x=a.length; i<x; i++) {
document.write(i + ' = ' + a[i]);
}
这将会写入:
0 = foo
1 = bar
截至2016年(ES6),我们可以使用 for...of
循环遍历数组,正如John Slegers已经注意到的那样。
我只想添加这个简单的演示代码,以使事情更清晰:
Array.prototype.foo = 1;
var arr = [];
arr[5] = "xyz";
console.log("for...of:");
var count = 0;
for (var item of arr) {
console.log(count + ":", item);
count++;
}
console.log("for...in:");
count = 0;
for (var item in arr) {
console.log(count + ":", item);
count++;
}
控制台显示:
for...of:
0: undefined
1: undefined
2: undefined
3: undefined
4: undefined
5: xyz
for...in:
0: 5
1: foo
换句话说:
for...of
从0到5进行计数,并忽略Array.prototype.foo
。它显示数组值。
for...in
仅列出5
,忽略未定义的数组索引,但添加了foo
。它显示数组属性名称。
简短回答:这并不值得。
较长的回答:即使不需要顺序元素顺序和最佳性能,这也不值得。
详细回答:这根本不值得……
for (var property in array)
会导致array
作为一个对象被迭代,遍历对象原型链,最终比基于索引的for
循环执行得慢。for (... in ...)
不能保证按预期的顺序返回对象属性。hasOwnProperty()
和!isNaN()
检查过滤对象属性是一种额外的开销,导致它执行得更慢,并抵消了首先使用它的关键原因,即更简洁的格式。由于这些原因,性能和方便之间的可接受折衷甚至不存在。除非意图将数组处理为对象并对数组的对象属性执行操作,否则没有任何好处。
length
、toString
等不包括在迭代中)。for (var ix=0;ix<arr.length;ix++) alert(ix);
然而,这里存在一个不同的问题。问题在于JavaScript数组中可能存在“空洞”。如果您将arr
定义为:
var arr = ["hello"];
arr[100] = "goodbye";
数组有两个元素,但长度为101。使用 for-in 循环会产生两个索引,而 for 循环将产生101个索引,其中99的值为undefined
。
除了其他回答中给出的原因,如果您需要对计数器变量进行数学运算,可能不希望使用“for...in”结构,因为循环遍历对象属性的名称,所以变量是字符串。
例如:
for (var i=0; i<a.length; i++) {
document.write(i + ', ' + typeof i + ', ' + i+1);
}
将要编写
0, number, 1
1, number, 2
...
相比之下,
for (var ii in a) {
document.write(i + ', ' + typeof i + ', ' + i+1);
}
会写
0, string, 01
1, string, 11
...
当然,这可以很容易地克服,只需包含相应的内容即可。
ii = parseInt(ii);
在循环中,但第一种结构更直接。
+
代替 parseInt
。 - Konrad BorowskiparseInt()
。尝试一下 parseInt("025");
,它会失败的。 - Derek 朕會功夫parseInt
。问题在于,如果你不包含基数(radix),旧版浏览器可能会尝试解释数字(因此025变成八进制)。这个问题在ECMAScript 5中得到了修复,但以“0x”开头的数字仍然会被解释为十六进制。为了保险起见,使用基数来指定数字,例如parseInt("025", 10)
- 这指定了十进制。 - IAmTimCoreyfor
...in
循环遍历所有可枚举属性(这与“所有数组元素”不同!)之外,参见http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf,第 5 版的第 12.6.4 节或第 7 版的第 13.7.5.15 节:
列举属性的机制和顺序未指定...
(强调是我的。)
这意味着如果浏览器愿意,它可以按照插入的顺序、数字顺序或字典顺序(其中“30”在“4”之前!请记住,所有对象键 - 因此所有数组索引 - 实际上都是字符串,所以这是完全合理的)遍历属性。如果实现对象作为哈希表,则可以按桶进行遍历。或者将任何内容添加到“反向”中。甚至浏览器可以随机迭代并符合 ECMA-262,只要它恰好访问每个属性一次。
实际上,大多数浏览器当前喜欢以大致相同的顺序迭代。但是没有任何东西表明它们必须这样做。这是实现特定的,如果发现另一种方法更有效,则可以随时更改。
无论哪种方式,for
...in
都不带有顺序的含义。如果您关心顺序,请明确说明并使用带有索引的常规 for
循环。
主要有两个原因:
第一
正如其他人所说,你可能会得到不在你的数组中或从原型中继承的键。所以,如果让我们说,一个库向数组或对象的原型添加了一个属性:
Array.prototype.someProperty = true
你将会在每个数组的一部分中得到它:
for(var item in [1,2,3]){
console.log(item) // will log 1,2,3 but also "someProperty"
}
您可以通过使用hasOwnProperty方法来解决这个问题:
var ary = [1,2,3];
for(var item in ary){
if(ary.hasOwnProperty(item)){
console.log(item) // will log only 1,2,3
}
}
但这对于使用for-in循环迭代任何对象都是正确的。
二
通常,数组中项目的顺序很重要,但是for-in循环不一定会按照正确的顺序进行迭代,因为它将数组视为一个对象,这是JS中实现的方式,而不是作为一个数组。 这似乎是一件小事,但它确实会破坏应用程序并且难以调试。
array.forEach
。请参阅Polyfill https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach - Qwerty
var i = hCol1.length; for (i;i;i--;) {}
,将 i 缓存起来会有所不同,并简化测试。对于老版本的浏览器而言,for
和while
的差异越大,因此请始终缓存 "i" 计数器。当然,负数并不总是适用于所有情况,并且while
在某些人看来会使代码更加难懂。另外,请注意var i = 1000; for (i; i; i--) {}
和var b =1000 for (b; b--;) {}
这两种写法,其中 i 从 1000 到 1,b 从 999 到 0。对于老版本的浏览器而言,while
倾向于使用for
以提高性能。 - Mark Schultheissfor(var i = 0, l = myArray.length; i < l; ++i) ...
是使用顺序迭代时最快和最好的选择。 - Mathieu Amiot