Вопрос: Почему использование «for ... in» с итерацией массива - плохая идея?


Мне сказали не использовать for...inс массивами в JavaScript. Почему нет?


1521


источник


Ответы:


Причина в том, что одна конструкция:

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
*/


1330



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
  }
}

354



Существует три причины, почему вы не должны использовать for..inдля итерации по элементам массива:

  • for..inбудет охватывать все собственные и унаследованные свойства объекта массива, которые не являются DontEnum; это означает, что если кто-то добавляет свойства к определенному объекту массива (есть веские причины для этого - я сделал это сам) или изменил Array.prototype(который считается плохой практикой в ​​коде, который должен хорошо работать с другими скриптами), эти свойства также будут повторяться; унаследованные свойства могут быть исключены путем проверки hasOwnProperty(), но это не поможет вам со свойствами, установленными в самом массиве

  • for..inне гарантируется сохранение упорядочения элементов

  • он медленный, потому что вам нужно пройти все свойства объекта массива и всей цепочки прототипов и все равно получить только имя свойства, то есть получить значение, потребуется дополнительный поиск


101



Потому что для ... в перечислении через объект, который содержит массив, а не сам массив. Если я добавлю функцию в цепочку прототипов массивов, это также будет включено. То есть

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 = бар 

48



В отдельности нет ничего плохого в использовании for-in на массивах. For-in выполняет итерации над именами свойств объекта, а в случае массива «из коробки» свойства соответствуют индексам массива. (Встроенные средства, такие как length, toStringи т. д. не включены в итерацию.)

Однако, если ваш код (или используемая структура) добавляет настраиваемые свойства к массивам или прототипу массива, то эти свойства будут включены в итерацию, что, вероятно, не то, что вы хотите.

Некоторые JS-структуры, такие как Prototype, модифицируют прототип Array. Других фреймворков, таких как JQuery, нет, поэтому с JQuery вы можете безопасно использовать in-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,


36



In addition to the reasons given in other answers, you may not want to use the "for...in" structure if you need to do math with the counter variable because the loop iterates through the names of the object's properties and so the variable is a string.

For example,

for (var i=0; i<a.length; i++) {
    document.write(i + ', ' + typeof i + ', ' + i+1);
}

will write

0, number, 1
1, number, 2
...

whereas,

for (var ii in a) {
    document.write(i + ', ' + typeof i + ', ' + i+1);
}

will write

0, string, 01
1, string, 11
...

Of course, this can easily be overcome by including

ii = parseInt(ii);

in the loop, but the first structure is more direct.


28



As of 2016 (ES6) we may use for…of for array iteration, as John Slegers already noticed.

I would just like to add this simple demonstration code, to make things clearer:

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++;
    }

The console shows:

for...of:

0: undefined
1: undefined
2: undefined
3: undefined
4: undefined
5: xyz

for...in:

0: 5
1: foo

In other words:

  • for...of counts from 0 to 5, and also ignores Array.prototype.foo. It shows array values.

  • for...in lists only the 5, ignoring undefined array indexes, but adding foo. It shows array property names.


23



Short answer: It's just not worth it.


Longer answer: It's just not worth it, even if sequential element order and optimal performance aren't required.


Long answer: It's just not worth it, for reasons following:

  • Using for (var i in array) {} will cause 'array' to be interpreted as any other pure object, traversing the object property chain and ultimately performing slower than an index-based for loop.
  • It's not guaranteed to return the object properties in sequential order as one might expect.
  • Using hasOwnProperty() or isNaN() checks to filter the object properties is an additional overhead causing it to perform (even more) slower. Also, introducing such additional logic negates the key reason for using it in the first place, i.e. because of the more concise format.

For these reasons an acceptable trade-off between performance and convenience doesn't even exist. Really, there's no benefit unless the intent is to treat the array as a pure object and performs operations on the array object's properties.


20



Aside from the fact that for...in loops over all enumerable properties (which is not the same as "all array elements"!), see http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf, section 12.6.4 (5th edition) or 13.7.5.15 (7th edition):

The mechanics and order of enumerating the properties ... is not specified...

(Emphasis mine.)

That means if a browser wanted to, it could go through the properties in the order in which they were inserted. Or in numerical order. Or in lexical order (where "30" comes before "4"! Keep in mind all object keys -- and thus, all array indexes -- are actually strings, so that makes total sense). It could go through them by bucket, if it implemented objects as hash tables. Or take any of that and add "backwards". A browser could even iterate randomly and be ECMA-262 compliant, as long as it visited each property exactly once.

In practice, most browsers currently like to iterate in roughly the same order. But there's nothing saying they have to. That's implementation specific, and could change at any time if another way was found to be far more efficient.

Either way, for...in carries with it no connotation of order. If you care about order, be explicit about it and use a regular for loop with an index.


20



Mainly two reasons:

One

Like others have said, You might get keys which aren't in your array or that are inherited from the prototype. So if, let's say, a library adds a property to the Array or Object prototypes:

Array.prototype.someProperty = true

You'll get it as part of every array:

for(var item in [1,2,3]){
  console.log(item) // will log 1,2,3 but also "someProperty"
}

you could solve this with the hasOwnProperty method:

var ary = [1,2,3];
for(var item in ary){
   if(ary.hasOwnProperty(item)){
      console.log(item) // will log only 1,2,3
   }
}

but this is true for iterating over any object with a for-in loop.

Two

Usually the order of the items in an array is important, but the for-in loop won't necessarily iterate in the right order, that's because it treats the array as an object, which is the way it is implemented in JS, and not as an array. This seems like a small thing, but it can really screw up applications and is hard to debug.


15



Because it enumerates through object fields, not indexes. You can get value with index "length" and I doubt you want this.


13