Чому використання "для ... в" з ітерацією масиву є поганою ідеєю?


1823

Мені сказали не використовувати for...inз масивами в JavaScript. Чому ні?


45
Я побачив недавнє запитання, де хтось сказав вам це, але вони мали на увазі лише для Arrays. Вважається поганою практикою ітерації через масиви, але не обов'язково для ітерації через членів об'єкта.
mmurch

19
Багато відповідей із циклами "для", наприклад "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
Історично деякі веб-переглядачі навіть повторювали "довжину", "тотальність" тощо!
bobince

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

78
Перше питання не є причиною, це погано, лише різниця в семантиці. Друге питання, мені здається, є причиною (крім сутичок між бібліотеками, що роблять те саме), що зміна прототипу вбудованого типу даних є поганою, а не тим, що для..in є поганою.
Стюарт

86
@Stewart: Усі об'єкти в JS асоціативні. JS масив - це об'єкт, так що так, він також асоціативний, але це не для чого. Якщо ви хочете перебрати ключі об'єкта , використовуйте 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

Тому що для ... в перерахуваннях через об'єкт, який утримує масив, а не сам масив. Якщо я додаю функцію до ланцюга прототипу масивів, вона також буде включена. Тобто

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 () {сигнал (це); }

А оскільки ви ніколи не можете бути впевнені, що до прототипу ланцюжок нічого не буде додано, просто використовуйте цикл для перерахунку масиву:

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

Це буде писати:

0 = foo
1 = бар

16
Масиви - це " Об'єкти", немає "об'єкта, що містить масив".
RobG

41

Ізоляційно, нічого поганого в використанні масивів for-in немає. Для ітерації імен властивостей об'єкта, а у випадку масиву "поза коробкою" властивості відповідають індексам масиву. (Вбудований в propertes , як length, toStringі так далі, не включаються до ітерації.)

Однак якщо ваш код (або рамка, яку ви використовуєте) додайте власні властивості до масивів або до прототипу масиву, то ці властивості будуть включені в ітерацію, що, мабуть, не є тим, що ви хочете.

Деякі рамки JS, як-от Prototype, модифікують прототип Array. Інших фреймворків, таких як JQuery, немає, тому з JQuery можна сміливо використовувати для входу.

Якщо ви сумніваєтесь, ви, ймовірно, не повинні використовувати for-in.

Альтернативним способом ітерації через масив є використання for-loop:

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


32

Коротка відповідь: Це просто не варто.


Більш довга відповідь: Це просто не варто, навіть якщо послідовний порядок елементів та оптимальна продуктивність не потрібні.


Довга відповідь: Це просто не варто ...

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

З цієї причини прийнятний компроміс між продуктивністю та зручністю навіть не існує. Насправді користі немає, якщо наміром є обробляти масив як об’єкт та виконувати операції над об’єктними властивостями масиву.


31

На додаток до причин, наведених в інших відповідях, ви, можливо, не хочете використовувати структуру "для ... в", якщо вам потрібно зробити математику зі змінною лічильника, оскільки цикл перебирається через імена властивостей об'єкта і так змінна - це рядок.

Наприклад,

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" (він інтерпретує число як шістнадцятковий). Щоб бути на захищеному боці, скористайтеся radix, щоб вказати таке число, 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.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, а не як масив. Це здається дрібницею, але це дійсно може накрутити програми та важко налагодити.


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

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

Ви можете навчити цих браузерів array.forEach, вставивши певний код у свої сценарії. Дивіться розробник
Qwerty

Звичайно, але тоді ти маніпулюєш прототипом, і це не завжди гарна ідея ... і все-таки у тебе є питання про наказ ...
Lior

І, звичайно, причина номер три: Рідкі масиви.
кращий олівер

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 самостійно.
vava

і практично кожна бібліотека має свій метод і для цього.
vava

5
Ця відповідь неправильна. "Довжина" не буде включена в ітерацію для введення. Включено лише ті властивості, які ви додаєте самі.
ЖакБ

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робить і дійсно потрібно, щоб побачити всі властивості, тож це теж добре.


Гарне пояснення Pointy. Просто цікаво. Якби у мене був масив, що знаходився всередині об'єкта з властивостями множення і робив for in, порівняно з регулярним циклом, ці масиви перебирали б? (що би по суті, повільна продуктивність, правда?)
NiCk Newman

2
@NiCkNewman добре, що об’єкт, на який ви посилаєтесь inу for ... inциклі, буде просто
Pointy

Розумію. Цікаво, тому що у мене в основному ігровому об'єкті є об'єкти та масиви, і мені було цікаво, чи для іннінгу це буде більш болісно, ​​ніж просто звичайний цикл для індексів.
NiCk Newman

@NiCkNewman добре темою всього цього питання є те, що ви просто не повинні використовувати for ... inна масивах; є багато вагомих причин цього не робити. Це не стільки проблема ефективності, скільки "переконайтеся, що вона не зламається"
Поні

Ну, мої об'єкти зберігаються в масиві технічно, тому я хвилювався, щось на кшталт: [{a:'hey',b:'hi'},{a:'hey',b:'hi'}]але так, я розумію.
NiCk Newman

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]випадкових індексів. Ви відразу помітите різницю. - >> Перевірте це << << .
Редукція

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'

Напевно, це не має великого значення. Елементи масиву - це властивості об’єкта на основі масиву або типу Array, а всі властивості об'єкта мають клавіші рядка. Якщо ваш двигун JS якимось чином не оптимізує його, навіть якщо ви використали число, воно в кінцевому підсумку перетвориться на рядок для пошуку.
cHao

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

8

Важливим аспектом є те, що for...inлише ітерація над властивостями, що містяться в об'єкті, має атрибут численних властивостей встановлений на істинне. Отже, якщо спробу перебрати об’єкт за допомогою об'єкта, for...inто довільні властивості можуть бути пропущені, якщо їх атрибути перечисленого властивості невірні. Цілком можливо змінити перелічуваний атрибут властивості для звичайних об'єктів масиву, щоб певні елементи не перерахували. Хоча загалом атрибути властивості, як правило, застосовуються до функціональних властивостей усередині об'єкта.

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

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теж
Безкоштовні консультації

2
for ... inпрацює з предметами. Немає такого поняття, як автоматичне виявлення.
кращий олівер

7

Тому що це буде повторювати властивості, що належать до об'єктів в ланцюзі прототипу, якщо ви не будете уважні.

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


2
Мало - цілком нормально додавати до масивів екземпляри довільних іменованих властивостей, і ті перевірятимуться за trueдопомогою hasOwnProperty()чеків.
Pointy

Хороший момент, дякую. Я ніколи не був досить дурним, щоб зробити це до масиву, тому я не вважав цього!
JAL

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

1
@Wynand цікава ідея; однак я насправді не розумію, чому це варто, коли ітерація простим числовим індексом так проста.
Pointy

@WynandB вибачте за помилку, але я відчуваю, що виправлення в порядку: isNaNце для перевірки, чи є змінна спеціальним значенням NaN чи ні, її не можна використовувати для перевірки "речей, відмінних від чисел" (ви можете перейти зі звичайною typeof для цього).
doldt

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 - "вище" немає, оскільки коментарі постійно змінюють порядок на сторінці. Отже, ми не знаємо, про який коментар ви хочете сказати. З цієї причини добре сказати «в коментарі x особи».
JAL

@JAL ... або додай постійну посилання на відповідь.
WynandB

5

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

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

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

3.) Стандарт для циклу буде виконувати функцію стільки разів, скільки ви визначаєте в параметрах, і оскільки масив пронумерований, має більш сенс визначити, скільки разів ви хочете виконати функцію. На відміну від інших циклів, цикл 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, але не для масиву, але все ж ми не можемо сказати, що це неправильний шлях, але це не рекомендується. Подивіться на цей приклад нижче, використовуючи для ... in loop:

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

A для ... in loop завжди перераховує ключі. Клавіші властивостей об'єктів завжди є String, навіть індексованими властивостями масиву:

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

1

Оскільки елементи JavaScript зберігаються як стандартні властивості об'єкта, не рекомендується проводити повторення через масиви JavaScript, використовуючи для ... у циклі, оскільки нормальні елементи та всі перелічені властивості будуть перераховані.

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


0

хоча це питання не стосується конкретно, я б додав, що є дуже вагомий привід ніколи не використовувати для ... in with 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.