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

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

Ответы [ 26 ]

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

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

15 голосов
/ 24 ноября 2010

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

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

13 голосов
/ 21 февраля 2016

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

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

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

Примечание:

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

9 голосов
/ 25 октября 2013

Кроме того, из-за семантики 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"
9 голосов
/ 28 января 2017

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

Я думаю, что цикл for in является жемчужиной 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:");
8 голосов
/ 10 мая 2013

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

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

myobject.propertyIsEnumerable('myproperty')

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

Object.getOwnPropertyDescriptor(myobject,'myproperty')

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

8 голосов
/ 24 ноября 2010

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

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]);
8 голосов
/ 06 июня 2012

В дополнение к другим проблемам, синтаксис 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 голосов
/ 24 ноября 2010

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

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

6 голосов
/ 24 ноября 2010

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

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

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

...