Почему использование «for ... in» с итерацией массива - плохая идея? - PullRequest
1691 голосов
/ 01 февраля 2009

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

Ответы [ 26 ]

1470 голосов
/ 01 февраля 2009

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

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
*/
371 голосов
/ 24 ноября 2010

Сам по себе оператор for-in не является "плохой практикой", однако он может быть неправильно использован , например, для итерации над массивами или объектами, похожими на массивы .

Цель оператора for-in состоит в том, чтобы перечислить по свойствам объекта. Это утверждение будет развиваться в цепочке прототипов, также перечисляя более унаследованные свойства, что иногда нежелательно.

Кроме того, порядок итерации не гарантируется спецификацией, что означает, что если вы хотите «перебрать» объект массива, с этим оператором вы не можете быть уверены, что свойства (индексы массива) будут посещены в числовом заказ.

Например, в JScript (IE <= 8) порядок перечисления даже для объектов Array определяется при создании свойств: </p>

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
  }
}
110 голосов
/ 01 февраля 2009

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

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

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

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

51 голосов
/ 01 февраля 2009

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

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 = bar
myOwnFunction = function() { alert(this); }

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

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

Это напишет:

0 = foo
1 = bar
39 голосов
/ 01 февраля 2009

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

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

Некоторые 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 даст 101 индекс, где значение 99 имеет значение undefined.

31 голосов
/ 02 сентября 2009

В дополнение к причинам, приведенным в других ответах, вы, возможно, не захотите использовать структуру «for ... in», если вам нужно выполнить математику с переменной counter, поскольку цикл повторяет имена свойств объекта и поэтому переменная является строкой.

Например,

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

в цикле, но первая структура более прямая.

30 голосов
/ 10 марта 2016

С 2016 года (ES6) мы можем использовать for…of для итерации массива, как уже заметил Джон Слегерс.

Я просто хотел бы добавить этот простой демонстрационный код, чтобы прояснить ситуацию:

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. Он показывает массив имен свойств .

24 голосов
/ 14 марта 2013

Краткий ответ: это просто не стоит.


Более длинный ответ: Это просто не стоит, даже если последовательный порядок элементов и оптимальная производительность не требуются.


Длинный ответ: Это просто не стоит, по следующим причинам:

  • Использование for (var property in array) приведет к тому, что array будет повторяться как объект , обходя цепочку прототипов объекта и в конечном итоге работая медленнее, чем цикл for на основе индекса.
  • for (... in ...) гарантированно не возвращает свойства объекта в последовательном порядке, как можно было бы ожидать.
  • Использование проверок hasOwnProperty() и !isNaN() для фильтрации свойств объекта - это дополнительные издержки, которые заставляют его работать (даже больше) медленнее и сводят на нет основную причину его использования в первую очередь, то есть из-за более сжатого формата .

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

23 голосов
/ 14 мая 2012

Помимо того факта, что for ... in зацикливается на всех перечисляемых свойствах (которые не совпадают с "всеми элементами массива"!), См. http://www.ecma -international.org/publications/files/ECMA-ST/Ecma-262.pdf, раздел 12.6.4 (5-е издание) или 13.7.5.15 (7-е издание):

Механика и порядок перечисления свойств ... не указан ...

(Акцент мой.)

Это означает, что если браузер хочет, он может просматривать свойства в том порядке, в котором они были вставлены. Или в порядке номеров. Или в лексическом порядке (где «30» предшествует «4»! Имейте в виду, что все ключи объектов - и, следовательно, все индексы массивов - фактически являются строками, так что это имеет смысл). Он мог бы проходить через них, если реализовывал объекты в виде хеш-таблиц. Или возьмите что-нибудь из этого и добавьте «назад». Браузер может даже выполнять итерацию случайным образом и быть совместимым с ECMA-262, если он посещает каждое свойство ровно один раз.

На практике большинству браузеров в настоящее время нравится выполнять итерации примерно в одном и том же порядке. Но ничто не говорит, что они должны. Это зависит от конкретной реализации и может измениться в любое время, если будет найден другой способ, более эффективный.

В любом случае, for ... in не несет в себе никакого смысла порядка. Если вы заботитесь о порядке, проясните его и используйте обычный цикл for с индексом.

16 голосов
/ 04 февраля 2014

Главным образом две причины:

Один

Как уже говорили другие, вы можете получить ключи, которых нет в вашем массиве или которые унаследованы от прототипа. Так что если, скажем, библиотека добавляет свойство к прототипам Array или Object:

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 а не как массив. Это кажется небольшой вещью, но она может действительно испортить приложения и ее трудно отладить.

...