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

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

1579
задан lYriCAlsSH 01 февр. '09 в 12:46
источник поделиться
25 ответов

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

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
*/
1376
ответ дан Triptych 01 февр. '09 в 13:08
источник поделиться

Операция 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
  }
}
359
ответ дан CMS 24 нояб. '10 в 0:22
источник поделиться

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

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

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

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

101
ответ дан Christoph 01 февр. '09 в 17:04
источник поделиться

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

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
49
ответ дан Pim Jager 01 февр. '09 в 13:08
источник поделиться

В изобилии нет ничего плохого в использовании in-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.

37
ответ дан JacquesB 01 февр. '09 в 13:34
источник поделиться

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

Например,

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

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

29
ответ дан ctmiddle 02 сент. '09 в 5:29
источник поделиться

Начиная с 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, игнорируя индексы массива undefined, но добавляя foo. Он показывает имена свойств массива .

26
ответ дан MarcG 10 марта '16 в 7:29
источник поделиться

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

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

(Подчеркните мой.)

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

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

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

21
ответ дан cHao 14 мая '12 в 19:26
источник поделиться

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


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


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

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

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

20
ответ дан WynandB 14 марта '13 в 10:12
источник поделиться

В основном две причины:

One

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

15
ответ дан Lior 04 февр. '14 в 19:54
источник поделиться

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

15
ответ дан vava 01 февр. '09 в 12:50
источник поделиться

Проблема с for ... in ... — и это становится проблемой, когда программист действительно не понимает язык; это не ошибка или что-то еще; заключается в том, что он выполняет итерацию по всем элементам объекта (ну, все перечисляемые члены, но эта деталь на данный момент). Если вы хотите перебирать только индексированные свойства массива, единственным гарантированным способом сохранения семантически согласованных значений является использование целочисленного индекса (т.е. Цикла стиля for (var i = 0; i < array.length; ++i)).

Любой объект может иметь связанные с ним произвольные свойства. Не было бы ничего страшного в загрузке дополнительных свойств в экземпляр массива, в частности. Код, который хочет видеть только индексированные свойства, подобные массиву, поэтому должен придерживаться целочисленного индекса. Код, который полностью знает, что for ... in действительно и действительно должен видеть все свойства, ну, тогда это тоже нормально.

14
ответ дан Pointy 24 нояб. '10 в 0:23
источник поделиться

Я не думаю, что мне есть что добавить. Ответ на триптих или ответ CMS о том, почему в некоторых случаях следует избегать использования for-in.

Однако я бы хотел добавить, что в современных браузерах есть альтернатива for-in, которая может использоваться в тех случаях, когда for-in не может использоваться. Эта альтернатива for-of:

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

Примечание:

К сожалению, ни одна версия Internet Explorer не поддерживает эту функцию (Edge 12+), поэтому вам придется подождать немного дольше пока вы не сможете использовать его в своем производственном коде на стороне клиента. Тем не менее, это должно быть безопасно использовать в JS-коде на стороне сервера (если вы используете Node.js).

10
ответ дан John Slegers 21 февр. '16 в 23:48
источник поделиться

Кроме того, из-за семантики путь for, in обрабатывает массивы (то есть, как и любой другой объект JavaScript) не выравнивается с другими популярными языками.

// 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"
8
ответ дан matpop 25 окт. '13 в 19:43
источник поделиться

TL & DR: Использование цикла for in в массивах не является злом, на самом деле все наоборот.

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

  • Он также проходит через унаследованные свойства: Прежде всего, любые расширения для Array.prototype должны были быть выполнены с помощью Object.defineProperty(), а их дескриптор enumerable должен быть установлен на false. Любая библиотека, которая не делает этого, не должна использоваться вообще.
  • Свойства, которые вы добавляете в цепочку наследования, затем подсчитываются: При выполнении подкласса массива на Object.setPrototypeOf или по классу extend. Вы должны снова использовать Object.defineProperty(), который по умолчанию устанавливает дескрипторы свойств writable, enumerable и 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. Цикл for in медленный: Ад. Это, безусловно, самый быстрый метод итерации, если вы перебираете разреженные массивы, которые требуются время от времени. Это один из самых важных трюков, которые нужно знать. Давайте посмотрим пример. Мы будем перебирать разреженный массив.

var a = [];
a[0] = "zero";
a[10000000] = "ten million";
console.time("for loop on array a:");
for(var i=0; i < a.length; i++) a[i] && console.log(a[i]);
console.timeEnd("for loop on array a:");
console.time("for in loop on array a:");
for(var i in a) a[i] && console.log(a[i]);
console.timeEnd("for in loop on array a:");
7
ответ дан Redu 28 янв. '17 в 11:16
источник поделиться

Важным аспектом является то, что for...in выполняет только итерации над свойствами, содержащимися в объекте, которые имеют атрибут свойства перечисляемый, установленный в true. Поэтому, если вы пытаетесь выполнить итерацию объекта с помощью for...in, тогда любые свойства могут быть пропущены, если их атрибут enumerable property равен false. Вполне возможно изменить атрибут enumerable property для обычных объектов Array, чтобы определенные элементы не были перечислены. Хотя, как правило, атрибуты свойств имеют тенденцию относиться к свойствам функции внутри объекта.

Можно проверить значение атрибута свойства enumerable свойства:

myobject.propertyIsEnumerable('myproperty')

Или получить все четыре атрибута свойства:

Object.getOwnPropertyDescriptor(myobject,'myproperty')

Это функция, доступная в ECMAScript 5 - в более ранних версиях было невозможно изменить значение атрибута enumerable property (он всегда был равен true).

7
ответ дан Pierz 10 мая '13 в 21:20
источник поделиться

В дополнение к другим проблемам синтаксис "for..in", вероятно, медленнее, потому что индекс - это строка, а не целое число.

var a = ["a"]
for (var i in a)
    alert(typeof i)  // 'string'
for (var i = 0; i < a.length; i++)
    alert(typeof i)  // 'number'
7
ответ дан dc1 06 июня '12 в 1:52
источник поделиться

for/in работает с двумя типами переменных: hashtables (ассоциативные массивы) и массив (неассоциативный).

JavaScript автоматически определит способ его прохождения через элементы. Поэтому, если вы знаете, что ваш массив действительно неассоциативный, вы можете использовать for (var i=0; i<=arrayLen; i++) и пропустить итерацию автоматического обнаружения.

Но, на мой взгляд, лучше использовать for/in, процесс, необходимый для этого автоматического обнаружения, очень мал.

Реальный ответ для этого будет зависеть от того, как браузер анализирует/интерпретирует код 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]);
7
ответ дан Ricardo 24 нояб. '10 в 0:44
источник поделиться

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

Вы можете использовать for.. in, просто обязательно проверьте каждое свойство с помощью hasOwnProperty.

6
ответ дан JAL 24 нояб. '10 в 0:20
источник поделиться

Вы должны использовать for(var x in y) только в списках свойств, а не на объектах (как описано выше).

5
ответ дан user268396 24 нояб. '10 в 0:25
источник поделиться

Это не обязательно плохо (на основе того, что вы делаете), но в случае массивов, если что-то добавлено в Array.prototype, тогда вы получите странные результаты. Где вы ожидаете, что этот цикл будет выполняться три раза:

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

Если в Array prototype добавлена ​​функция с именем helpfulUtilityMethod, тогда ваш цикл будет работать четыре раза: key будет 0, 1, 2 и helpfulUtilityMethod. Если вы ожидали только целых чисел, oops.

5
ответ дан josh3736 24 нояб. '10 в 0:25
источник поделиться

Использование цикла for...in для массива не является неправильным, хотя я могу догадаться, почему кто-то сказал вам, что:

1.) Существует уже функция или метод более высокого порядка, который имеет эту цель для массива, но имеет больше функциональности и более компактный синтаксис, называемый "forEach": Array.prototype.forEach(function(element, index, array) {} );

2.) Массивы всегда имеют длину, но for...in и forEach не выполняют функцию для любого значения 'undefined', только для индексов, которые имеют определенное значение. Поэтому, если вы назначаете только одно значение, эти циклы будут выполнять только один раз один раз, но поскольку перечисляется массив, он всегда будет иметь длину до наивысшего индекса, имеющего определенное значение, но эта длина может остаться незамеченной при использовании этих петли.

3.) Стандарт для цикла будет выполнять функцию столько раз, сколько вы определяете в параметрах, и поскольку массив пронумерован, имеет смысл определить, сколько раз вы хотите выполнить функцию. В отличие от других циклов, цикл for может затем выполнять функцию для каждого индекса в массиве, независимо от того, определено это значение или нет.

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

Кроме того, лучше всего использовать метод forEach, чем цикл for...in в целом, поскольку его легче писать и имеет больше функциональных возможностей, поэтому вы можете захотеть привыкнуть только к используя этот метод и стандарт для, но ваш вызов.

См. ниже, что первые две петли выполняют только операторы console.log один раз, в то время как стандарт for loop выполняет функцию столько раз, сколько указано, в этом случае 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]
3
ответ дан mrmaclean89 17 сент. '17 в 2:19
источник поделиться

для... в полезен при работе над объектом в JavaScript, но не для массива, но мы не можем сказать это неправильно, но это не рекомендуется, посмотрите на это пример ниже, используя цикл для... в:

let txt = "";
const person = {fname:"Alireza", lname:"Dezfoolian", age:35}; 
for (const x in person) {
    txt += person[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35 

ОК, сделайте это с помощью Массив:

let txt = "";
const person = ["Alireza", "Dezfoolian", 35]; 
for (const x in person) {
   txt += person[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35 

Как вы видите результат тот же самый...

Но попробуй что-нибудь, пусть прототип чего-то Массив...

Array.prototype.someoneelse = "someoneelse";

Теперь мы создаем новый массив Array();

let txt = "";
const arr = new Array();
arr[0] = 'Alireza';
arr[1] = 'Dezfoolian';
arr[2] = 35;
for(x in arr) {
 txt += arr[x] + " ";
}
console.log(txt); //Alireza Dezfoolian 35 someoneelse

Вы видите someelse!!!... Мы в этом случае перебираем новый объект Array!

Итак, одна из причин, по которой нам нужно использовать для... внутри, но это не всегда так...

1
ответ дан Alireza 25 дек. '17 в 16:38
источник поделиться

A for... in loop всегда перечисляет ключи. Ключами свойств объектов всегда являются String, даже индексированные свойства массива:

var myArray = ['a', 'b', 'c', 'd'];
var total = 0
for (elem in myArray) {
  total += elem
}
console.log(total); // 00123
1
ответ дан Maher Tliba 04 мая '18 в 19:10
источник поделиться

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

От https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections

0
ответ дан Badr Elmers 14 янв. '18 в 4:23
источник поделиться

Другие вопросы по меткам или Задайте вопрос