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

for-in语句本身并不是一个“坏习惯”,但它可能被错误地用来 遍历数组或类似数组的对象。

for-in语句的目的是枚举对象属性。 这个语句将在原型链中继续,也列举了inheritance的属性, 有时不是所希望的。

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

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

 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 } 

另外,说到inheritance的属性,例如,如果你扩展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循环。

当你只想枚举一个对象的属性 (那些没有被inheritance的属性 )时,你可以使用hasOwnProperty方法:

 for (var prop in obj) { if (obj.hasOwnProperty(prop)) { // prop is not inherited } } 

有些人甚至build议直接从Object.prototype调用该方法,以避免有人向我们的对象添加名为hasOwnProperty的属性时出现问题:

 for (var prop in obj) { if (Object.prototype.hasOwnProperty.call(obj, prop)) { // prop is not inherited } } 

有三个原因,你不应该使用for..in迭代数组元素:

  • for..in将循环所有不属于DontEnum的数组对象的所有属性和inheritance属性; 这意味着如果有人向特定的数组对象添加属性(这是有原因的,我自己也是这样做的),或者改变了Array.prototype (在代码中被认为是不好的做法,它可以和其他脚本一起工作),这些属性也将迭代; 可以通过检查hasOwnProperty()来排除inheritance的属性,但是这不会帮助你使用数组对象本身的属性

  • for..in不能保证元素sorting

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

因为…在枚举通过持有数组的对象,而不是数组本身。 如果我添加一个函数到数组原型链,那也将包括在内。 即

 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 = foo
 1 =酒吧
 myOwnFunction = function(){alert(this);  }

既然你永远不能确定没有什么会被添加到原型链只是使用for循环来枚举数组:

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

这将写:

 0 = foo
 1 =酒吧

孤立地说,在数组中使用for-in没有任何问题。 For-in迭代对象的属性名称,对于“开箱即用”数组,则属性对应于数组索引。 (内置的lengthtoString等属性不包括在迭代中。)

但是,如果您的代码(或您使用的框架)将自定义属性添加到数组或数组原型,则这些属性将包含在迭代中,这可能不是您想要的。

一些JS框架,比如Prototype修改了Array原型。 其他框架如JQuery不支持,所以使用JQuery,您可以安全地使用for-in。

如果你有疑问,你可能不应该使用for-in。

迭代数组的另一种方法是使用for循环:

 for (var ix=0;ix<arr.length;ix++) alert(ix); 

但是,这有一个不同的问题。 问题是JavaScript数组可能有“漏洞”。 如果你将arr定义为:

 var arr = ["hello"]; arr[100] = "goodbye"; 

然后该数组有两个项目,但长度为101.使用for-in将产生两个索引,而for-loop将产生101个索引,其中99个具有undefined的值。

除了其他答案中给出的原因之外,如果需要使用countervariables进行math运算,则可能不希望使用“for … in”结构,因为循环遍历对象属性的名称,所以variables是一个string。

例如,

 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); 

在循环中,但第一个结构更直接。

从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 for...of计数,也忽略Array.prototype.foo 。 它显示数组

  • for...in只列出5 ,忽略未定义的数组索引,但添加foo 。 它显示数组属性名称

简短的回答:这是不值得的。


较长的答案:即使不需要顺序元素顺序和最佳性能,这也是不值得的。


长答案:这是不值得的,原因如下:

  • 使用for (var i in array) {}将导致“数组”被解释为任何其他对象,遍历对象属性链并最终执行比基于索引的for循环慢。
  • 不能保证按照预期的顺序返回对象属性。
  • 使用hasOwnProperty()isNaN()检查来过滤对象属性是一个额外的开销,导致它执行(甚至更多)更慢。 另外,引入这样的附加逻辑首先否定了使用它的关键原因,即由于更简洁的格式。

由于这些原因,在性能和便利之间可以接受的折衷甚至不存在。 实际上,除非意图将数组视为对象并对数组对象的属性执行操作,否则没有任何好处。

除了…的所有枚举属性的循环之外(与“所有数组元素” 一样),请参阅http://www.ecma-international.org/publications/files/ECMA-ST /Ecma-262.pdf ,第12.6.4节(第5版)或13.7.5.15(第7版):

枚举属性的机制和顺序未指定

(强调我的)

这意味着,如果浏览器想要,它可以通过它们插入的顺序通过属性。 或按数字顺序。 或者按照词汇顺序(“30”在“4”之前)!记住所有的对象键 – 因此,所有的数组索引 – 实际上是string,因此总是有意义的)。 如果它将对象实现为散列表,它可以通过桶来完成。 或采取任何一个,并添加“倒退”。 浏览器甚至可以随机迭代,并且符合ECMA-262,只要它访问每个属性一次。

实际上,目前大多数浏览器喜欢按大致相同的顺序进行迭代。 但是没有什么要说的。 这是具体实现,如果发现另一种方法效率更高,可以随时更改。

无论哪种方式, for …都没有秩序的内涵。 如果你关心的是顺序,请明确指出它,并使用一个带有索引的常规循环。

主要有两个原因:

就像其他人所说的那样,您可能会得到不在您的数组中或从原型inheritance的密钥。 所以,如果让我们说,一个库添加一个属性的数组或对象的原型:

 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中实现的方式,而不是作为一个数组。 这似乎是一个小东西,但它可以真正搞砸了应用程序,很难debugging。

因为它通过对象字段枚举,而不是索引。 你可以得到索引“长度”的价值,我怀疑你想要这个。

for ... in ...遇到的问题 – 当程序员不真正理解语言时,这只会成为一个问题; 它并不是一个真正的bug或者其他的东西 – 它是遍历一个对象的所有成员(好吧,所有可枚举的成员,但这是现在的一个细节)。 当你想迭代一个数组的索引属性时,保持语义上一致的唯一保证方法是使用一个整数索引(即,一个for (var i = 0; i < array.length; ++i)样式循环)。

任何对象都可以有与之相关的任意属性。 特别是将附加属性加载到数组实例上没有什么可怕的。 希望看到索引数组属性的代码因此必须坚持一个整数索引。 代码是完全知道什么for ... in做,真的需要看到所有的属性,那么也没关系。

除了其他问题之外,“for..in”语法可能更慢,因为索引是一个string,而不是一个整数。

 var a = ["a"] for (var i in a) alert(typeof i) // 'string' for (var i = 0; i < a.length; i++) alert(typeof i) // 'number' 

for / in使用两种types的variables:散列表(关联数组)和数组(非关联)。

JavaScript会自动确定它通过项目的方式。 所以如果你知道你的数组真的是非关联的,你可以使用for (var i=0; i<=arrayLen; i++) ,并跳过自动检测迭代。

但在我看来,使用for / in更好,自动检测所需的进程非常小。

这个真正的答案将取决于浏览器parsing器/解释JavaScript代码的方式。 它可以在浏览器之间切换。

我想不出其他的目的不要用for / in ;

 //Non-associative var arr = ['a', 'b', 'c']; for (var i in arr) alert(arr[i]); //Associative var arr = { item1 : 'a', item2 : 'b', item3 : 'c' }; for (var i in arr) alert(arr[i]); 

因为如果你不小心的话,它会迭代属于原型链上的对象的属性。

您可以使用for.. in ,只要确保使用hasOwnProperty检查每个属性。

一个重要的方面是for...in只包含在一个对象中的属性设置为true的迭代。 因此,如果尝试迭代使用for...in的对象for...in那么任意属性可能会错过,如果它们的enumerable属性为false。 很有可能改变普通数组对象的enumerable属性,这样就不会枚举某些元素。 尽pipe通常属性属性往往适用于对象内的函数属性。

可以通过以下方法检查属性的enumerable属性的值:

 myobject.propertyIsEnumerable('myproperty') 

或者获取全部四个属性属性:

 Object.getOwnPropertyDescriptor(myobject,'myproperty') 

这是ECMAScript 5中可用的function – 在早期版本中,无法更改enumerable属性的值(它总是设置为true)。

而且,由于语义的原因for, in对待数组(与其他任何JavaScript对象相同)时,其方式与其他stream行语言不一致。

 // C# char[] a = new char[] {'A', 'B', 'C'}; foreach (char x in a) System.Console.Write(x); //Output: "ABC" // Java char[] a = {'A', 'B', 'C'}; for (char x : a) System.out.print(x); //Output: "ABC" // PHP $a = array('A', 'B', 'C'); foreach ($a as $x) echo $x; //Output: "ABC" // JavaScript var a = ['A', 'B', 'C']; for (var x in a) document.write(x); //Output: "012" 

我想我没有太多补充,例如。 在三联公司的答案或CMS的答案为什么使用for-in原因应该被避免在某些情况下。

不过,我确实想补充一点, 在现代浏览器 for-in ,可以使用for-in的替代scheme, for-in不能使用for-in情况下使用。 该替代scheme是for-of

 for (var item of items) { console.log(item); } 

注意 :

不幸的是,没有Internet Explorer版本支持此function( Edge 12+ ),所以您必须等待一会儿,直到您可以在客户端生产代码中使用它。 但是,在服务器端JS代码中使用应该是安全的(如果使用Node.js的话 )。

不一定是坏的(基于你在做什么),但是在数组的情况下,如果已经添加了Array.prototype ,那么你将得到奇怪的结果。 你希望这个循环运行三次:

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

如果一个名为helpfulUtilityMethod的函数已经添加到Arrayprototype ,那么你的循环最终会运行四次: key将是0helpfulUtilityMethod 。 如果你只是期待整数,哎呀。

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

TL&DR:在数组中使用for in循环并不是坏事,实际上恰恰相反。

我认为如果在数组中使用正确for in循环是JS的gem。 您需要完全控制您的软件,并知道您在做什么。 让我们看看所提到的缺点,并逐一反驳它们。

  1. 它通过inheritance的属性循环:首先, Array.prototype的所有扩展应该使用Object.defineProperty()完成,并且它们的enumerable描述符应该设置为false 。 任何不这样做的图书馆都不应该使用。
  2. 以后添加到inheritance链的属性会被计算在内:当通过Object.setPrototypeOf或Class Object.setPrototypeOf进行数组子类化时。 您应该再次使用Object.defineProperty() ,默认情况下将writableenumerableenumerable configurable属性描述符设置为false 。 让我们看一个数组子类的例子…
 function Stack(...a){ var stack = new Array(...a); Object.setPrototypeOf(stack, Stack.prototype); return stack; } Stack.prototype = Object.create(Array.prototype); // now stack has full access to array methods. Object.defineProperty(Stack.prototype,"constructor",{value:Stack}); // now Stack is a proper constructor Object.defineProperty(Stack.prototype,"peak",{value: function(){ // add Stack "only" methods to the Stack.prototype. return this[this.length-1]; } }); var s = new Stack(1,2,3,4,1); console.log(s.peak()); s[s.length] = 7; console.log("length:",s.length); s.push(42); console.log(JSON.stringify(s)); console.log("length:",s.length); for(var i in s) console.log(s[i]); 

虽然我可以猜测为什么有人告诉你,为数组使用for...in循环没有错,

1.)已经有一个更高阶的函数或方法,它具有数组的目的,但是有更多的function和精简的语法,称为“forEach”: Array.prototype.forEach(function(element, index, array) {} );

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

3.)循环的标准将会执行一个函数,就像你在参数中定义的次数一样,并且由于一个数组被编号,所以定义你想要执行一个函数的次数是更有意义的。 与其他循环不同,for循环可以为数组中的每个索引执行一个函数,无论该值是否被定义。

实质上,你可以使用任何循环,但是你应该记得它们是如何工作的。 了解不同循环重申的条件,各自的function,并认识到它们或多或less会适用于不同的情况。

另外,一般来说,使用forEach方法可能会比for...in循环更好,因为它更容易编写,并且具有更多function,所以您可能希望养成只使用此方法的习惯和标准的,但你的电话。

请参阅下面的内容,前两个循环只执行一次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]