协慌网

登录 贡献 社区

为什么在数组迭代中使用 “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
*/

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迭代数组元素有三个原因:

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

  • for..in不保证保留元素排序

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