为什么在数组迭代中使用“for...in”不是一个好主意?

2073

我被告知在JavaScript中不要使用for...in循环遍历数组。为什么?


53
我看到最近有人对你说了这句话,但是他们只是针对数组而言。在遍历数组时进行迭代被认为是一种不好的做法,但在遍历对象成员时并不一定如此。 - mmurch
22
很多答案中使用了“for”循环,例如'for (var i=0; i<hColl.length; i++) {}',与'var i=hColl.length; while (i--) {}'相比较,当可以使用后者时,它的速度要快得多。我知道这可能离题了,但还是想加上这一点。 - Mark Schultheiss
3
@MarkSchultheiss 但那是反向迭代。 有没有另一种更快的正向迭代版本? - ma11hew28
5
@Wynand 使用 var i = hCol1.length; for (i;i;i--;) {},将 i 缓存起来会有所不同,并简化测试。对于老版本的浏览器而言,forwhile 的差异越大,因此请始终缓存 "i" 计数器。当然,负数并不总是适用于所有情况,并且 while 在某些人看来会使代码更加难懂。另外,请注意 var i = 1000; for (i; i; i--) {}var b =1000 for (b; b--;) {} 这两种写法,其中 i 从 1000 到 1,b 从 999 到 0。对于老版本的浏览器而言,while 倾向于使用 for 以提高性能。 - Mark Schultheiss
12
“你也可以聪明一些”。 for(var i = 0, l = myArray.length; i < l; ++i) ... 是使用顺序迭代时最快和最好的选择。 - Mathieu Amiot
显示剩余2条评论
29个回答

6

根据你的操作,这并不一定是坏事,但在数组的情况下,如果有东西被添加到Array.prototype中,那么你会得到奇怪的结果。本来你希望循环运行三次:

var arr = ['a','b','c'];
for (var key in arr) { ... }

如果在数组的原型上添加了名为helpfulUtilityMethod的函数,则您的循环将运行四次: key 会是012helpfulUtilityMethod。如果您只期望整数,那就糟糕了。

6

您应该仅在属性列表上使用for(var x in y),而不是在对象上使用(如上所述)。


13
关于 Stack Overflow 的一点说明 - 没有所谓的“上面”,因为评论在页面上经常更改顺序。因此,我们不知道您指的是哪个评论。出于这个原因,最好说“在 x 人的评论中”。 - JAL

5
使用for...in循环遍历数组并不是错误的,但我可以猜测为什么有人告诉你这个:

1.) 对于数组来说,已经存在一个更高级的函数或方法,名为“forEach”,它具有更多的功能和更简洁的语法:Array.prototype.forEach(function(element, index, array) {} );

2.) 数组总是有一个长度,但是for...inforEach不会执行任何值为'undefined'的函数,只会为那些具有定义值的索引执行函数。所以,如果你只分配了一个值,这些循环只会执行一次函数,但由于数组是枚举的,它将始终具有长度,直到具有定义值的最高索引位置,但在使用这些循环时,这个长度可能被忽略。

3.) 标准的for循环将根据参数定义的次数执行函数,并且由于数组是编号的,因此更有意义的是定义要执行函数的次数。与其他循环不同,for循环可以为数组中的每个索引执行函数,无论值是否被定义。

本质上,你可以使用任何循环,但是你应该记住它们的工作方式。了解不同循环迭代的条件、它们各自的功能,并意识到它们在不同情况下更或少适用。

此外,通常使用forEach方法而不是for...in循环被认为是更好的实践,因为它更容易编写并具有更多的功能,因此你可能想养成只使用这种方法和标准for循环的习惯,但这取决于你自己。

如下所示,前两个循环仅执行一次console.log语句,而标准for循环根据指定的次数执行函数,在本例中,array.length = 6。

var arr = [];
arr[5] = 'F';

for (var index in arr) {
console.log(index);
console.log(arr[index]);
console.log(arr)
}
// 5
// 'F'
// => (6) [undefined x 5, 6]

arr.forEach(function(element, index, arr) {
console.log(index);
console.log(element);
console.log(arr);
});
// 5
// 'F'
// => Array (6) [undefined x 5, 6]

for (var index = 0; index < arr.length; index++) {
console.log(index);
console.log(arr[index]);
console.log(arr);
};
// 0
// undefined
// => Array (6) [undefined x 5, 6]

// 1
// undefined
// => Array (6) [undefined x 5, 6]

// 2
// undefined
// => Array (6) [undefined x 5, 6]

// 3
// undefined
// => Array (6) [undefined x 5, 6]

// 4
// undefined
// => Array (6) [undefined x 5, 6]

// 5
// 'F'
// => Array (6) [undefined x 5, 6]

4

for...in循环始终枚举键名。

对象属性的键名始终为字符串,即使是数组的索引属性也是如此:

var myArray = ['a', 'b', 'c', 'd'];
var total = 0
for (elem in myArray) {
  total += elem
}
console.log(total); // 00123

2

for...in 在 JavaScript 中对于对象很有用,但对于数组则不太适用。虽然我们不能说这是错误的方式,但也不推荐使用。请看下面使用 for...in 循环的示例:

let txt = "";
const person = {fname:"Alireza", lname:"Dezfoolian", age:35}; 
for (const x in person) {
    txt += person[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35 

好的,现在让我们使用数组来完成它:

let txt = "";
const person = ["Alireza", "Dezfoolian", 35]; 
for (const x in person) {
   txt += person[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35 

正如您所看到的结果一样...

但是,让我们尝试一些东西,让我们原型化一些 Array...

Array.prototype.someoneelse = "someoneelse";

现在我们创建一个新的 Array();
let txt = "";
const arr = new Array();
arr[0] = 'Alireza';
arr[1] = 'Dezfoolian';
arr[2] = 35;
for(x in arr) {
 txt += arr[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35 someoneelse

你看到了someoneelse!!!...在这种情况下,我们实际上是循环遍历新的Array对象!

因此,这就是为什么我们需要小心使用for..in的原因之一,但并不总是如此...


2

1
虽然这个问题没有明确提到,但我想补充一点:不要使用for...in与NodeList一起使用(例如从querySelectorAll调用中获取),因为它根本不会看到返回的元素,只会迭代NodeList属性。
在只有一个结果的情况下,我得到了:
var nodes = document.querySelectorAll(selector);
nodes
▶ NodeList [a._19eb]
for (node in nodes) {console.log(node)};
VM505:1 0
VM505:1 length
VM505:1 item
VM505:1 entries
VM505:1 forEach
VM505:1 keys
VM505:1 values

这解释了为什么我的 for (node in nodes) node.href = newLink; 失败了。

0
我刚发现这个。 JavaScript 确实是一门为自由的人而设计的语言,ROCK,没有规则。起初容易,最后难。哈哈
Array.prototype.foo = 1;
var a = [];
a[5] = 5;
console.log(a.length)          // 6
a.map(e => console.log(e))     // 5
a.forEach(e => console.log(e)) // 5
for(let i in a) console.log(i) // 5, foo
for(let i of a) console.log(i) // undefined..., foo

0

在for循环中,当遍历数组时,索引会被转换为字符串。例如,在下面的代码中,在第二个循环中将j初始化为i+1时,i是索引,但是以字符串形式表示(“0”,“1”等),而在js中数字和字符串相加得到的结果也是字符串。如果js遇到“0”+1,它将返回“01”。

var maxProfit = function(prices) {
  let maxProfit = 0;
  for (let i in prices) {
    for (let j = i + 1; j < prices.length; j++) {
      console.log(prices[j] - prices[i], "i,j", i, j, typeof i, typeof j);
      if ((prices[j] - prices[i]) > maxProfit) maxProfit = (prices[j] - prices[i]);
    }
  }
  return maxProfit;
};

maxProfit([7, 1, 5, 3, 6, 4]);


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接