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


1825

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


45
Я видел недавний вопрос, где кто-то сказал вам это, но они предназначались только для массивов. Это считается плохой практикой для итерации по массивам, но не обязательно для итерации по элементам объекта.
ммурч

19
Множество ответов с помощью циклов for, таких как for (var i = 0; i <hColl.length; i ++) {}, сравнивают с var i = hColl.length; тогда как (i--) {} ', который, когда возможно использовать последнюю форму, значительно быстрее. Я знаю, что это тангенциально, но думал, что добавлю это немного.
Марк Шультхайс

2
@MarkSchultheiss, но это обратная итерация. Есть ли другая версия прямой итерации, которая быстрее?
ma11hew28

5
@Wynand использовать var i = hCol1.length; for (i;i;i--;) {}кеш i, как это будет иметь значение, и упростить тест. - чем старше браузер, тем больше различие между forи whileВСЕГДА кеширующим счетчиком «i» - и, конечно, отрицательное не всегда соответствует ситуации, а отрицательное в то время obfuscate как код немного для некоторых людей. и обратите внимание, var i = 1000; for (i; i; i--) {}и var b =1000 for (b; b--;) {}где я перехожу от 1000 до 1, а b - от 999 до 0. - чем старше браузер, тем больше в то же время склоняется к производительности.
Марк Шультхайс,

9
Вы также можете быть умным. for(var i = 0, l = myArray.length; i < l; ++i) ...это самый быстрый и лучший способ получить итерацию вперед.
Матье Амиот

Ответы:


1557

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

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


146
Исторически, некоторые браузеры даже итерировали по «длине», «toString» и т. Д.!
bobince

398
Не забудьте использовать, (var x in a)а не (x in a)- не хотите создавать глобальный.
Крис Морган

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

86
@Stewart: все объекты в JS являются ассоциативными. JS Array - это объект, так что да, он тоже ассоциативный, но не для этого. Если вы хотите перебрать ключи объекта , используйте for (var key in object). Однако, если вы хотите перебрать элементы массива , используйте for(var i = 0; i < array.length; i += 1).
Мартейн

42
В первом примере вы сказали, что он перебирает числовые индексы от 0 до 4, как и все ожидают , я ожидаю , что он будет перебирать от 0 до 5 ! Поскольку, если вы добавите элемент в позицию 5, массив будет иметь 6 элементов (5 из них не определены).
stivlo

393

Само 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
  }
}

10
См. Также пост Дэвида Хамфри. Итерации по объектам в JavaScript быстро - для массивов for..inнамного медленнее, чем «нормальных» циклов.
Крис Морган

17
Вопрос о последнем пункте о hasOwnProperty: если кто-то переопределит hasOwnProperty объекта, у вас будут проблемы. Но не будет ли у вас таких же проблем, если кто-то переопределит «Object.prototype.hasOwnProperty»? В любом случае, они облажались, и это не ваша ответственность, верно?
Скотт Риппи

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

4
@ScottRippey: Если вы хотите взять его там: youtube.com/watch?v=FrFUI591WhI
Натан Уолл,

с помощью этого ответа я обнаружил, что могу получить доступ к значению с помощьюfor (var p in array) { array[p]; }
equiman

117

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

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

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

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


55

Потому что 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 = бар
myOwnFunction = function () {alert (this); }

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

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

Это напишет:

0 = foo
1 = бар

16
Массивы являются объектами, нет «объекта, который содержит массив».
RobG

41

В изоляции нет ничего плохого в использовании for-in для массивов. For-in перебирает имена свойств объекта, а в случае массива «из коробки» свойства соответствуют индексам массива. (Встроенный в propertes , как 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.


37

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


32

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


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


Длинный ответ: это просто не стоит ...

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

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


31

В дополнение к причинам, приведенным в других ответах, вы, возможно, не захотите использовать структуру «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);

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


6
Вы можете использовать префикс +вместо, parseIntесли вам действительно не нужно целое число или игнорировать недопустимые символы.
Конрад Боровски

Кроме того, использование parseInt()не рекомендуется. Попробуйте, parseInt("025");и это не удастся.
Дерек 朕 會 功夫

6
@Derek 朕 會 功夫 - вы определенно можете использовать parseInt. Проблема в том, что если вы не включите основание, старые браузеры могут попытаться интерпретировать число (таким образом, 025 становится восьмеричным). Это было исправлено в ECMAScript 5, но это все еще происходит для чисел, начинающихся с «0x» (он интерпретирует число как шестнадцатеричное). Чтобы быть в безопасности, используйте основание, чтобы указать число, например, так parseInt("025", 10): оно определяет базу 10.
IAmTimCorey

23

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


18

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

Один

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


2
Object.keys(a).forEach( function(item) { console.log(item) } )переберите массив собственных ключей свойств, а не тех, которые унаследованы от прототипа.
Qwerty

2
Правда, но, как и в цикле for-in, он не обязательно будет в правильном порядке индекса. Кроме того, он не будет работать на старых браузерах, не поддерживающих ES5.
Лиор

Вы можете обучить эти браузеры array.forEach, вставив определенный код в ваши скрипты. См. Polyfill developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Qwerty

Конечно, но тогда вы манипулируете прототипом, и это не всегда хорошая идея ... и тем не менее, у вас есть проблема порядка ...
Лиор

И, конечно же, причина номер три: разреженные массивы.
лучший Оливер

16

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


Так каков лучший способ сделать это?
lYriCAlsSH

3
for (var i = 0; i <arr.length; i ++) {}
vava

3
В Firefox 3 вы также можете использовать arr.forEach или for (var [i, v] в Iterator (arr)) {}, но ни одна из этих функций не работает в IE, хотя вы можете написать метод forEach самостоятельно.
Вава

и практически у каждой библиотеки есть свой метод для этого тоже.
Вава

5
Этот ответ неверен. длина не будет включена в итерацию for-in. Это только свойства, которые вы добавляете сами, которые включены.
JacquesB

16

Я не думаю, что мне есть что добавить, например. Ответ Триптиха или ответ 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 ).


@georgeawg Вы имели в виду for-of, не так for-inли?
ᆼ ᆺ ᆼ

15

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

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


Хорошее объяснение Пойнти. Просто любопытно. Если бы у меня был массив, который был внутри объекта со свойствами умножения и имел бы for in, по сравнению с обычным циклом for, эти массивы были бы перебраны? (что по сути будет медленной производительностью, верно?)
NiCk Newman

2
@NiCkNewman также объект вы ссылаетесь после inв for ... inцикле будет просто
Заостренный

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

@NiCkNewman хорошо, тема всего этого вопроса в том, что вы просто не должны использовать for ... inмассивы; Есть много веских причин не делать этого. Это не столько проблема производительности, сколько проблема «убедитесь, что она не сломалась».
Заостренный

Ну, технически мои объекты хранятся в массиве, поэтому я волновался, что-то вроде:, [{a:'hey',b:'hi'},{a:'hey',b:'hi'}]но да, я понимаю.
NiCk Ньюман

9

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

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


@Ravi Shanker Reddy Хороший бенчмаркинг настроен. Как я уже упоминал в своем ответе, for inцикл затмевает другие, «если» массив редок, и чем больше, тем больше его размер. Поэтому я переставил стендовый тест для разреженного массива, arrразмером ~ 10000, только с 50 предметами, случайно выбранными из [42,"test",{t:1},null, void 0]случайных индексов. Вы сразу заметите разницу. - >> Проверьте это здесь << - .
Redu

8

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

Вероятно, не имеет большого значения. Элементы массива - это свойства объекта на основе массива или объекта, подобного массиву, и все свойства объекта имеют строковые ключи. Если ваш движок JS каким-то образом не оптимизирует его, даже если вы используете число, оно в конечном итоге превратится в строку для поиска.
Цао

Независимо от каких-либо проблем с производительностью, если вы новичок в JavaScript, используйте var i in aи ожидаете, что индекс будет целочисленным, то выполнение чего-то подобного a[i+offset] = <value>будет помещать значения в совершенно неправильные места. («1» + 1 == «11»).
szmoore

8

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

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

myobject.propertyIsEnumerable('myproperty')

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

Object.getOwnPropertyDescriptor(myobject,'myproperty')

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


8

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

истина, если вы не используете прототипы объектов. ;) ниже
Рикардо

Это потому , что Arrayэто Objectслишком
Free Consulting

2
for ... inработает с объектами. Там нет такого понятия, как автоопределение.
лучший Оливер

7

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

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


2
Недостаточно - вполне нормально добавлять произвольные именованные свойства в экземпляры массива, и они будут проверяться trueпри hasOwnProperty()проверках.
Заостренный

Хороший вопрос, спасибо. Я никогда не был настолько глуп, чтобы делать это самому массиву, поэтому я не учел это!
JAL

1
@Pointy Я не проверял это, но, возможно, это можно преодолеть с помощью isNaNпроверки каждого имени свойства.
WynandB

1
@ Вин и интересная идея; однако я не понимаю, почему это стоит того, чтобы итерация с простым числовым индексом была такой простой.
Заостренный

@WynandB извините за удар, но я чувствую, что исправление в порядке: isNaNдля проверки, является ли переменная специальным значением NaN или нет, она не может использоваться для проверки «вещей, отличных от чисел» (вы можете пойти с обычным typeof для этого).
Дольдт

6

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

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

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


6

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


13
Просто примечание о SO - «выше» нет, потому что комментарии постоянно меняют порядок на странице. Итак, мы не знаем, какой комментарий вы имеете в виду. По этой причине хорошо сказать «в комментарии х человека».
JAL

@JAL ... или добавьте постоянную ссылку к ответу.
WynandB

5

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

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

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

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

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

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

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

4

Вот причины, по которым это (обычно) плохая практика:

  1. for...inциклы перебирают все свои перечисляемые свойства и перечислимые свойства своих прототипов. Обычно в итерации массива мы хотим перебирать только сам массив. И даже если вы сами ничего не можете добавить в массив, ваши библиотеки или фреймворк могут что-то добавить.

Пример :

Array.prototype.hithere = 'hithere';

var array = [1, 2, 3];
for (let el in array){
    // the hithere property will also be iterated over
    console.log(el);
}

  1. for...inциклы не гарантируют определенный порядок итераций . Хотя этот порядок обычно наблюдается в большинстве современных браузеров, гарантия 100% все еще отсутствует.
  2. for...inЦиклы игнорируют undefinedэлементы массива, то есть элементы массива, которые еще не были назначены.

Пример :

const arr = []; 
arr[3] = 'foo';   // resize the array to 4
arr[4] = undefined; // add another element with value undefined to it

// iterate over the array, a for loop does show the undefined elements
for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
}

console.log('\n');

// for in does ignore the undefined elements
for (let el in arr) {
    console.log(arr[el]);
}


2

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

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

Хорошо, теперь сделаем это с Array :

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

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

Но давайте попробуем что-то, давайте создадим что-то для Array ...

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

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

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


2

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

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

1

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

Из https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections


0

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

в случае одного результата я получил:

var nodes = document.querySelectorAll(selector);
nodes
 NodeList [a._19eb]
for (node in nodes) {console.log(node)};
VM505:1 0
VM505:1 length
VM505:1 item
VM505:1 entries
VM505:1 forEach
VM505:1 keys
VM505:1 values

который объяснил, почему мой for (node in nodes) node.href = newLink;провал.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.