有人告诉我不要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..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 = 条
从 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
. 它显示数组属性名称。