为什么使用“for...in”进行数组迭代是个坏主意?

IT技术 javascript arrays loops for-loop iteration
2021-01-03 16:06:23

有人告诉我不要for...in在 JavaScript 中使用数组。为什么不?

6个回答

原因是一种构造:

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)- 不想创建全局。
2021-02-25 16:06:23
我认为如果将 a[5] 设置为 5 以外的值,例如 a[5]=42,示例会更加清晰。第二个示例(“for (x in a)”仅枚举一个值这一事实对我来说并不奇怪;它枚举的值为 5 而不是 42 的事实对我来说是令人惊讶的(来自具有类似结构的其他语言)枚举列表内容而不是数组索引)。
2021-02-25 16:06:23
你说第一个例子,它迭代从 0 到 4 的数字索引,正如每个人所期望的,我希望它从 0 迭代到 5因为如果在位置 5 添加一个元素,数组将有 6 个元素(其中 5 个未定义)。
2021-03-03 16:06:23
第一个问题不是它不好的原因,只是语义上的差异。第二个问题在我看来是一个原因(除了库之间的冲突之外),改变内置数据类型的原型是不好的,而不是 for..in 是坏的。
2021-03-04 16:06:23
@Stewart:JS 中的所有对象都是关联的。JS 数组是一个对象,所以是的,它也是关联的,但这不是它的用途。如果要迭代对象的,请使用for (var key in object). 但是,如果要遍历数组的元素,请使用for(var i = 0; i < array.length; i += 1).
2021-03-09 16:06:23

for-in语句本身并不是一个“坏习惯”,但是它可能会被误用,例如,迭代数组或类数组对象。

for-in语句的目的枚举对象属性。这个语句将在原型链中上升,也会枚举继承的属性,这有时是不想要的。

此外,规范不保证迭代的顺序,这意味着如果您想“迭代”一个数组对象,使用此语句您不能确定将按数字顺序访问属性(数组索引)。

例如,在 JScript (IE <= 8) 中,即使在 Array 对象上的枚举顺序也被定义为创建的属性:

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的不是坏习惯,但它可以被滥用。您是否有一个真实世界的良好实践示例,您确实想要查看对象的所有属性,包括继承的属性?
2021-02-13 16:06:23
有了这个答案,我发现可以访问该值 for (var p in array) { array[p]; }
2021-02-20 16:06:23
@ScottRippey:如果你想把它带到那里:youtube.com/watch? v=FrFUI591WhI
2021-02-26 16:06:23
另请参阅 David Humphrey 的文章快速迭代 JavaScript 中的对象- 因为数组的for..in循环比“正常”循环慢得多。
2021-03-02 16:06:23
关于“hasOwnProperty”的最后一点的问题:如果有人在对象上覆盖“hasOwnProperty”,您就会遇到问题。但是如果有人覆盖“Object.prototype.hasOwnProperty”,你会不会遇到同样的问题?不管怎样,他们都把你搞砸了,这不是你的责任,对吧?
2021-03-08 16:06:23

不应该使用for..in迭代数组元素的三个原因

  • for..in将遍历数组对象的所有自己的和继承的属性,这些属性不是DontEnum这意味着如果有人将属性添加到特定的数组对象(这有正当的理由 - 我自己已经这样做了)或更改Array.prototype(这在应该与其他脚本一起工作的代码中被认为是不好的做法),这些属性将也被迭代;可以通过检查排除继承的属性hasOwnProperty(),但这对您在数组对象本身中设置的属性没有帮助

  • for..in 不能保证保留元素顺序

  • 它很慢,因为您必须遍历数组对象及其整个原型链的所有属性,并且仍然只能获取属性的名称,即要获取值,需要进行额外的查找

因为 for...in 枚举了保存数组的对象,而不是数组本身。如果我向数组原型链中添加一个函数,它也将被包含在内。IE

Array.prototype.myOwnFunction = function() { alert(this); }
a = new Array();
a[0] = 'foo';
a[1] = 'bar';
for(x in a){
 document.write(x + ' = ' + a[x]);
}

这将写:

0 = 富
1 = 条
myOwnFunction = function() { alert(this); }

而且由于您永远无法确定原型链中不会添加任何内容,因此只需使用 for 循环来枚举数组:

for(i=0,x=a.length;i<x;i++){
 document.write(i + ' = ' + a[i]);
}

这将写:

0 = 富
1 = 条
因此,在这种情况下,更好的说法是:“不要在数组上使用 for-in,因为它会包含您添加到原型中的任何内容”?
2021-02-25 16:06:23

从 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. 它显示数组属性名称