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

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

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

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

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

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

например, в JScript (IE

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 object (как и некоторые библиотеки, как 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 будет перебирать все собственные и унаследованные свойства объекта array, которые не являются DontEnum; это означает, что если кто - то добавляет свойства к конкретному объекту массива (есть веские причины для этого-я сделал это сам) или изменил Array.prototype (что считается плохой практикой в коде, который должен хорошо работать с другими скриптами), эти свойства будут повторяется также; унаследованные свойства могут быть исключены путем проверки hasOwnProperty(), но это не поможет вам со свойствами, установленными в самом объекте массива

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

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


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

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

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

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

некоторые фреймворки JS, такие как Prototype, изменяют прототип массива. Другие фреймворки, такие как JQuery, нет, поэтому с JQuery вы можете безопасно использовать for-in.

если вы сомневаетесь, вы, вероятно, не должны использовать for-in.

альтернативный способ итерации через массив использует for-loop:

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

однако, это имеет другую проблему. Проблема в том, что массив JavaScript может иметь "дыры". Если вы определить arr as:

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

тогда массив имеет два элемента, но длину 101. Использование for-in даст два индекса, в то время как for-loop даст 101 индекс, где 99 имеет значение undefined.


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

например,

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 для итерации массива, как Джон уже 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. Он показывает массив имена свойств.


помимо того, что 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 цикл с индексом.


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


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


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

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

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


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

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


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

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

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

Примечание :

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


кроме того, из-за семантики, как 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"

в дополнение к другим проблемам синтаксис "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'

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

можно проверить значение атрибута перечисляемого свойства:

myobject.propertyIsEnumerable('myproperty')

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

Object.getOwnPropertyDescriptor(myobject,'myproperty')

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


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

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

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

A реальный ответ на это будет зависеть от того, как браузер анализирует/интерпретирует код 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]);

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

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

  1. он также проходит через унаследованные свойства: прежде всего любые расширения Array.prototype должно было быть сделано с помощью Object.defineProperty() и enumerable дескриптор должен быть установлен на false. Любая библиотека, которая этого не делает, вообще не должна использоваться.
  2. свойства, которые вы добавляете в цепочку наследования позже, подсчитываются: при выполнении подкласса массива по 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:");

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

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


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

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

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


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


С помощью for...in цикл для массива не ошибается, хотя я могу догадаться, почему кто-то сказал вам, что:

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

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

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

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

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

см. ниже, что первые два цикла выполняют только консоль.log операторы один раз, в то время как стандартный цикл for выполняет функцию столько раз, сколько указано, в этом случае array.длина = 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]

Для ... в полезно при работе над объектом в 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";

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

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

видишь someoneelse!!!... В этом случае мы фактически зацикливаемся на новом объекте Array!

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


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

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


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

var myArray = ['a', 'b', 'c', 'd'];
var total = 0
for (elem in myArray) {
  total += elem
}
console.log(total); // 00123