JavaScript для… in vs for


461

Як ви вважаєте, чи є велика різниця в ... в і для петель? Який тип «за» ви віддаєте перевагу і чому?

Скажімо, у нас є масив асоціативних масивів:

var myArray = [{'key': 'value'}, {'key': 'value1'}];

Тож ми можемо повторити:

for (var i = 0; i < myArray.length; i++)

І:

for (var i in myArray)

Я не бачу великої різниці. Чи є якісь проблеми з продуктивністю?


13
Зверніть увагу , що ми також, від JS 1.6 , маємо: myArray.forEach(callback[, thisarg]).
Бенджі XVI

14
@Benji array.forEach насправді знаходиться в ES5.
mikemaccana

2
у циклі для if(myArray.hasOwnProperty(i)){true}
входу

6
['foo', 'bar', 'baz'].forEach(function(element, index, array){ console.log(element, index, array); }); добре використовувати майже всюди, окрім IE8, і це, безумовно, найвишуканіший синтаксис
Jon z

5
У ECMAScript 6 також є for...ofзаява , наприклад:for (let i of myArray) console.log(i);
Віталій Федоренко

Відповіді:


548

Вибір має ґрунтуватися на тому, яка ідіома найкраще зрозуміла.

Масив ітератується за допомогою:

for (var i = 0; i < a.length; i++)
   //do stuff with a[i]

Об'єкт, що використовується як асоціативний масив, ітератується за допомогою:

for (var key in o)
  //do stuff with o[key]

Якщо у вас немає причин руйнування землі, дотримуйтесь встановленої схеми використання.


38
Слід зазначити, що це приємна практика використовувати для ... in з фільтруванням if оператором. Існує зручний метод Object "obj.hasOwnProperty (member)", який перевіряє, чи повернутий ітератором член є фактично членом об'єкта. Дивіться: javascript.crockford.com/code.html
Дамір Зекич

57
Як коментується в іншій відповіді, "для ... в" не працює належним чином для масивів, тому що вона повторює всі властивості та методи масиву. Таким чином, ви повинні використовувати "для ... в" лише для ітерації властивостей об'єкта. В іншому випадку дотримуйтесь "for (i = 0; i <something; i ++)"
Denilson Sá Maia

З міркувань продуктивності IMO краще оцінювати довжину масиву до for, а не оцінювати a.length кожен раз у циклі.
UpTheCreek

9
@UpTheCreek: Це, безумовно, вірно, коли масив насправді щось повертає HTMLDOM, однак мені цікаво, наскільки великим повинен бути стандартний масив javascript, щоб ви могли помітити помітну різницю? Особисто я хотів би тримати код максимально простим до тих пір, поки не буде доведено необхідність зробити щось інше.
AnthonyWJones

2
@Pichan Я думаю, ви маєте на увазі i < l, ні i < a, у вашому стані для циклу.
Макс Нанасі

161

Дуглас Крокфорд рекомендує в JavaScript: Хороші частини (стор. 24), щоб не використовувати for inоператор.

Якщо ви використовуєте for inдля перегляду імен властивостей об'єкта, результати не впорядковані. Гірше: ви можете отримати несподівані результати; вона включає членів, успадкованих від прототипу ланцюга та назву методів.

Все, крім властивостей, можна відфільтрувати .hasOwnProperty. Цей зразок коду робить те, що ви, мабуть, спочатку хотіли:

for (var name in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, name)) {
        // DO STUFF
    }
}

70
for ... in цілком підходить для перегляду властивостей об'єкта. Це не підходить для циклічного перегляду елементів масиву. Якщо ви не можете зрозуміти різницю між цими сценаріями, то так, вам слід уникати для ... в; в іншому випадку горіхи.
Shog9

8
Хочете підкреслити той факт, що це НЕ ЗАМОВЛЕНО! Це може бути великою проблемою, і помилку буде важко зловити.
Джейсон

4
+1 для "вона включає членів, успадкованих від прототипу ланцюга та ім'я методів". Ви отримаєте задоволення, якщо хтось, наприклад, використовує ваш код із завантаженим прототипом (навіть якщо ваш код фактично не використовує його), наприклад.
ijw

13
Будь ласка , не забудьте оголосити про nameзмінної: for(var name in object)..., в іншому випадку, якщо цей код знаходиться всередині функції, наприклад, то nameзмінна в кінцевому підсумку властивість глобального об'єкта (привласнення неоголошеної ідентифікатора робить це), а також в новому ECMAScript 5 Суворий режим, що код буде кидати a ReferenceError.
CMS

6
@Nosredna: Існує питання про порядок ітерації Chrome, поданий ніхто іншим, ніж John Resig, який позначений як WontFix. code.google.com/p/chromium/isissue/detail?id=883 . Ще до хромування порядок ітерації не був однаковим у веб-переглядачах, якщо ви видалите та знову додаєте властивість. Також IE 9 дуже схожий на хром (нібито для покращення швидкості). Тож ... Будь ласка, перестаньте поширювати неточну інформацію, ви б дуже наївно зберігали її залежно.
Хуан Мендес

62

Користувачі FYI - jQuery


each(callback)Метод jQuery використовує for( ; ; )цикл за замовчуванням і використовуватиме for( in ) лише якщо довжина undefined.

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

Приклад :

$(['a','b','c']).each(function() {
    alert(this);
});
//Outputs "a" then "b" then "c"

Мінус використання цього полягає в тому, що якщо ви виконуєте певну логіку інтерфейсу, ваші функції будуть менш переносими для інших фреймворків. Ця each()функція, ймовірно, найкраще зарезервована для використання з селекторами jQuery, і for( ; ; )може бути доцільно в іншому випадку.



4
Завжди є documentcloud.github.com/underscore, який має _.each та ще безліч інших корисних функцій
w00t

1
це також означає, що якщо у мене в об'єкті $ $ .each є збій властивості довжини? наприклад, x = {a: "1", b: "2", довжина: 3}.
Онур Топал

29

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

Наприклад:

for (var i = myArray.length-1; i >= 0; i--)

у деяких браузерах майже вдвічі швидший, ніж:

for (var i = 0; i < myArray.length; i++)

Однак, якщо ваші масиви НЕ ВЕЛИЧЕЗНІ або ви постійно цикли їх, всі досить швидко. Я серйозно сумніваюся, що циклічність масиву є вузьким місцем у вашому проекті (або будь-якому іншому проекті з цього питання)


5
Чи зберігання "myArray.length" у змінній перед циклом зробить різницю в продуктивності? Моя здогадка - «так».
Томалак

3
Ні. "MyArray.length" - це властивість, а не метод на об'єкті - для визначення його значення не проводиться розрахунок. Збереження його значення у змінній взагалі нічого не зробить.
Ясон

3
Так, є. Властивості не є змінними; у них є код get / set.
ste

12
Я схильний використовуватиfor(var i = myArray.length; i--;)
Кевін

2
Код для наочності та читабельності. Не для того, що в деяких браузерах може бути запущено незначно швидше при використанні абсурдно масивних масивів у поточний момент часу. Оптимізація може змінитися, але читабельність вашого коду (або його відсутність) не буде. Напишіть код, який інші можуть легко дотримуватися, і нехай оптимізатори наздоганяють свій власний час.
aroth

26

Зауважте, що нативний метод Array.forEach зараз широко підтримується .


2
Що це робить? Чи є проблеми, про які йдеться в інших публікаціях (перекидання властивостей замість елементів масиву)? Крім того, оскільки IE8 не підтримує його, трохи розтягнутись, можна сказати, що він широко підтримується.
Рауні Ліллемець

2
стільки, скільки * nix користувачі зневажають це, IE8 - це найбільше користувачів sub-windows7. ось велика частина ринку браузерів.
sbartell

2
@Rauni - Я вважаю, що для настільних пристроїв частка браузера IE становить менше 40%, відповідно до en.wikipedia.org/wiki/Usage_share_of_web_browsers#Summary_table , а відповідно до Markethare.hitslink.com/… та інших сайтів, принаймні 8% веб-переглядачів - це IE 9. Іншими словами, Array.forEach підтримується близько 70% браузерів настільних комп'ютерів, тому я не думаю, що «широко підтримувані» є нерозумними. Я не перевіряв, але підтримка мобільних пристроїв (у веб-переглядачах WebKit та Opera) може бути навіть вищою. Очевидно, що в географічному відношенні існують значні зміни.
Сем Даттон

1
Дякуємо за оновлення. Я погоджуюся, що можна сказати, що це "широко підтримується". Єдина проблема полягає в тому, що якщо користувач використовує цей метод JS, він / вона все одно повинен написати резервний метод для випадку, якщо він не підтримується ..
Rauni Lillemets

1
@Rauni - ви можете використовувати es5-shim та es6-shim для автоматичного надання резервних методів. github.com/es-shims/es5-shim
Michiel van der Blonk

24

Оновлена ​​відповідь для поточної версії 2012 року для всіх основних браузерів - Chrome, Firefox, IE9, Safari та Opera підтримує вроджений масив ES5.forEach.

Якщо у вас немає певних причин підтримувати IE8 на власному рівні (маючи на увазі, ES5-shim або Chrome кадр може бути наданий цим користувачам, що забезпечить належне середовище JS), то чистіше просто використовувати відповідний синтаксис мови:

myArray.forEach(function(item, index) {
    console.log(item, index);
});

Повна документація для array.forEach () знаходиться в MDN.


1
Ви дійсно повинні задокументувати параметри зворотного виклику: 1-е значення елемента, 2-е індекс елемента, 3-й масив, який проходить,
мальовничий

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

1
@Cory: ES5 forEach можна легко додати до застарілих браузерів ES3. Менше коду - краще код.
mikemaccana

2
@nailer Чи можна це використовувати на масивах та об'єктах взаємозамінно?
hitautodestruct

1
@hitautodestruct Це частина прототипу Array, а не Object's. Як правило, у спільноті зараз об'єкти, що не належать до масиву, все ще перебираються з "for (ключ ключа в об'єкті) {}".
mikemaccana

14

Два не однакові, коли масив розріджений.

var array = [0, 1, 2, , , 5];

for (var k in array) {
  // Not guaranteed by the language spec to iterate in order.
  alert(k);  // Outputs 0, 1, 2, 5.
  // Behavior when loop body adds to the array is unclear.
}

for (var i = 0; i < array.length; ++i) {
  // Iterates in order.
  // i is a number, not a string.
  alert(i);  // Outputs 0, 1, 2, 3, 4, 5
  // Behavior when loop body modifies array is clearer.
}

14

Використовуючи forEach для пропуску прототипу ланцюга

Просто швидке доповнення до відповіді @ nailer вище , використовуючи forEach за допомогою Object.keys, ви можете уникнути повторення прототипу по ланцюжку без використання hasOwnProperty.

var Base = function () {
    this.coming = "hey";
};

var Sub = function () {
    this.leaving = "bye";
};

Sub.prototype = new Base();
var tst = new Sub();

for (var i in tst) {
    console.log(tst.hasOwnProperty(i) + i + tst[i]);
}

Object.keys(tst).forEach(function (val) {
    console.log(val + tst[val]);
});

2
чорт, це підлий. Варто було прочитати ще 50 публікацій, щоб дійти до цього. obj = {"pink": "качки", червоний: "гуси"}; Object.keys (obj) === ["рожевий", "червоний"]
Orwellophile

14

По-друге, я вважаю, що ви повинні вибрати метод ітерації відповідно до своїх потреб. Я хотів би запропонувати вам на самому ділі не коли - або прохідний виходець Arrayз for inструктурою. Це набагато повільніше і , як зазначав Чейз Сейберт в даний момент тому, не сумісний з рамкою прототипу.

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

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

l = ''; for (m in obj) { l += m + ' => ' + obj[m] + '\n' } console.log(l);

Він скидає вміст цілого об'єкта (разом із органами методів) до мого журналу Firebug. Дуже зручно.


Зараз посилання розірвано. Звичайно, хотіли б бачити тест, якщо хтось має інше посилання.
Білбад

Це вже не зламано.
Оллі

Цикли прориву в прототипі? Як це часто підтримується, це щось прототип має вирішувати.
mvrak

7

ось щось я зробив.

function foreach(o, f) {
 for(var i = 0; i < o.length; i++) { // simple for loop
  f(o[i], i); // execute a function and make the obj, objIndex available
 }
}

ось як би ви його використовували, це
буде працювати над масивами та об'єктами (наприклад, список елементів HTML)

foreach(o, function(obj, i) { // for each obj in o
  alert(obj); // obj
  alert(i); // obj index
  /*
    say if you were dealing with an html element may be you have a collection of divs
  */
  if(typeof obj == 'object') { 
   obj.style.marginLeft = '20px';
  }
});

Я щойно зробив це, тому я відкритий для пропозицій :)


Чудові речі - досить просто!
Кріс

6

Я б використовував різні методи, засновані на тому, як я хотів посилатися на елементи.

Використовуйте foreach, якщо ви просто хочете поточний предмет.

Використовуйте для, якщо вам потрібен індексатор для порівняльних порівнянь. (Тобто, як це порівнюється з попереднім / наступним пунктом?)

Я ніколи не помічав різниці у виконанні. Я б зачекав, поки не виникне проблема з виступом, перш ніж переживати про це.


Дивіться відповідь Бноса нижче - бо ... в цьому не робиться того, що ви очікуєте тут, і якщо ви ним скористаєтесь, ви, можливо, отримаєте всі розваги. Для запису Prototype робить все правильно .
marcus.greasly

4

З допомогою for (var i в myArray) ви також можете переходити на об'єкти, я буду містити ім'я ключа, і ви можете отримати доступ до властивості через myArray [i] . Крім того, будь-які методи, які ви додали до об'єкта, також будуть включені в цикл, тобто якщо ви використовуєте будь-який зовнішній фреймворк, наприклад jQuery або прототип, або якщо ви додаєте методи до об'єктів прототипів безпосередньо, в один момент я вкажу на ці методи.


4

Стережись!

Якщо у вас є кілька тегів сценарію, і ви шукаєте інформацію, наприклад, в атрибутах тегів, вам потрібно використовувати властивість .length з циклом for, оскільки це не простий масив, а об’єкт HTMLCollection.

https://developer.mozilla.org/en/DOM/HTMLCollection

Якщо ви використовуєте оператор foreach для (var i у вашому списку), він поверне властивості та методи HTMLCollection у більшості браузерів!

var scriptTags = document.getElementsByTagName("script");

for(var i = 0; i < scriptTags.length; i++)
alert(i); // Will print all your elements index (you can get src attribute value using scriptTags[i].attributes[0].value)

for(var i in scriptTags)
alert(i); // Will print "length", "item" and "namedItem" in addition to your elements!

Навіть якщо getElementsByTagName повинен повернути NodeList, більшість браузерів повертає HTMLCollection: https://developer.mozilla.org/en/DOM/document.getElementsByTagName


3

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

http://www.prototypejs.org/api/array


Забудьте "можливо, вам доведеться використовувати цю бібліотеку". Подумайте, натомість "ваш JS може бути включений до всього іншого, що використовує цю бібліотеку", тому що проблеми все ще виникають.
ijw

3

Я бачив проблеми з "для кожного" за допомогою об'єктів, прототипу та масивів

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


3

Якщо ви дійсно хочете пришвидшити свій код, що з цим?

for( var i=0,j=null; j=array[i++]; foo(j) );

якось мати логіку в той час як для оператора, і це менш зайве. Також у firefox є Array.forEach та Array.filter


2
Чому це пришвидшило б ваш код? Я не можу зрозуміти, чому перенаписання таких тверджень пришвидшило б її.
Rup

3

Коротший і найкращий код відповідно до jsperf

keys  = Object.keys(obj);
for (var i = keys.length; i--;){
   value = obj[keys[i]];// or other action
}

1

Використовуйте цикл Array (). ForEach, щоб скористатися паралелізмом


4
JavaScript у браузері є циклом подій одночасно, тому Array.prototype.forEachне може виконувати кілька дзвінків до зворотного дзвінка паралельно.
Майк Самуель


0

Будь обережний!!! Я використовую Chrome 22.0 в Mac OS, і у мене виникають проблеми з кожним синтаксисом.

Я не знаю, чи це проблема з браузером, проблема JavaScript або якась помилка в коді, але це ДУЖЕ дивно. Поза предметом він прекрасно працює.

var MyTest = {
    a:string = "a",
    b:string = "b"
};

myfunction = function(dicts) {
    for (var dict in dicts) {
        alert(dict);
        alert(typeof dict); // print 'string' (incorrect)
    }

    for (var i = 0; i < dicts.length; i++) {
        alert(dicts[i]);
        alert(typeof dicts[i]); // print 'object' (correct, it must be {abc: "xyz"})
    }
};

MyObj = function() {
    this.aaa = function() {
        myfunction([MyTest]);
    };
};
new MyObj().aaa(); // This does not work

myfunction([MyTest]); // This works

0

Існує важлива різниця між обома. Вхідне ітераціює над властивостями об'єкта, тому, коли справа є масивом, він не лише повторюватиметься над його елементами, але й над функцією "видалити", яку він має.

for (var i = 0; i < myArray.length; i++) { 
    console.log(i) 
}

//Output
0
1

for (var i in myArray) { 
    console.log(i) 
} 

// Output
0 
1 
remove

Ви можете скористатися фор-ін за допомогою if(myArray.hasOwnProperty(i)). Тим не менш, при ітерації над масивами я завжди вважаю за краще уникати цього і просто використовувати оператор for (;;).


0

Хоча вони обоє дуже схожі, є незначна різниця:

var array = ["a", "b", "c"];
array["abc"] = 123;
console.log("Standard for loop:");
for (var index = 0; index < array.length; index++)
{
  console.log(" array[" + index + "] = " + array[index]); //Standard for loop
}

у цьому випадку вихід:

СТАНДАРТ ДО LOOP:

АРЕЙ [0] = A

АРЕЙ [1] = В

АРЕЙ [2] = С

console.log("For-in loop:");
for (var key in array)
{
  console.log(" array[" + key + "] = " + array[key]); //For-in loop output
}

при цьому вихідний результат:

ЗАПУСК:

АРЕЙ [1] = В

АРЕЙ [2] = С

АРЕЙ [10] = D

АРЕЙ [ABC] = 123


0

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.