Для кожного з масиву в JavaScript?

Як я можу переглянути всі записи в масиві за допомогою JavaScript?

Я подумав, що це щось на зразок цього:

 forEach(instance in theArray) 

Де theArray - мій масив, але це здається неправильним.

4041
17 февр. заданий Dante1986 17 февр. 2012-02-17 16:51 '12 о 16:51 2012-02-17 16:51
@ 32 відповідей
  • 1
  • 2

TL; DR

  • Не використовуйте for-in якщо ви не використовуєте його з захистом або, по крайней мере, не знаєте, чому він може вас вкусити.
  • Ваші кращі ставки зазвичай

    • for-of loop (тільки для ES2015 +),
    • Array#forEach ( spec | MDN ) (або його родичі some і такі) (тільки ES5 +),
    • простий старомодний for цикли,
    • або for-in входу з захистом.

Але є ще багато, щоб досліджувати, читати далі ...


JavaScript має потужну семантику для циклічного перетворення масивів і об'єктів типу масиву. Я розділив відповідь на дві частини: варіанти для справжніх масивів і варіанти для речей, які схожі тільки на масиви, такі як об'єкт arguments , інші ітеріруемие об'єкти (ES2015 +), колекції DOM і т.д.

Я швидко заметлю, що тепер ви можете використовувати опції ES2015, навіть на двигунах ES5, шляхом пересилання ES2015 на ES5. Знайдіть "ES2015 transpiling" / "ES6 transpiling" для більш ...

Добре, подивимося на наші варіанти:

Для реальних масивів

У вас є три варіанти в ECMAScript 5 ( "ES5"), версія, найбільш широко підтримувана на даний момент, і ще дві додані в ECMAScript 2015 ( "ES2015", "ES6"):

  1. Використовувати forEach і пов'язані з ним (ES5 +)
  2. Використовуйте простий for циклу
  3. Правильно використовувати for-in
  4. Використовувати for-of (використовувати итератор неявно) (ES2015 +)
  5. Використовувати итератор явно (ES2015 +)

деталі:

1. Використовувати forEach і пов'язані з ним

У будь-якій невизначено сучасному середовищі (так, а не в IE8), де у вас є доступ до функцій Array доданим ES5 (безпосередньо або з використанням поліполненій), ви можете використовувати forEach ( spec | MDN ):

 var a = ["a", "b", "c"]; a.forEach(function(entry) { console.log(entry); }); 

forEach приймає функцію зворотного виклику і, необов'язково, значення, що використовується як this при виклику цього зворотного виклику (не використовується вище). Як передзвонити викликається для кожного запису в масиві, щоб пропустити неіснуючі записи в розріджених масивах. Хоча я використовував тільки один аргумент вище, зворотний виклик викликається з трьома: значення кожного запису, індекс цього запису і посилання на масив, який ви повторюєте (в разі, якщо ваша функція ще не має його).

Якщо ви не підтримуєте застарілі браузери, такі як IE8 (які NetApps показує на рівні більше 4% ринку на момент написання цієї статті у вересні 2016 року), ви можете з радістю використовувати forEach на універсальної веб-сторінці без прокладки. Якщо вам потрібно підтримувати застарілі браузери, легко виконати операцію shimming / polyfilling forEach (знайдіть "es5 shim" для кількох опцій).

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

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

Крім того, forEach - це функція "loop through them all", але ES5 визначив кілька інших корисних "проробок через функції масиву і речей", в тому числі:

  • every (зупиняє цикл в перший раз, коли зворотний виклик повертає false або щось помилкове)
  • some (зупиняє цикл в перший раз, коли зворотний виклик повертає true або щось правдоподібне)
  • filter (створює новий масив, що включає елементи, в яких функція фільтра повертає true і опускає ті, де вона повертає false )
  • map (створює новий масив із значень, що повертаються зворотним викликом)
  • reduce (нарощує значення, повторно викликаючи зворотний виклик, передаючи попередні значення, див. специфікацію для деталей, корисно для підсумовування вмісту масиву і багатьох інших речей)
  • reduceRight (наприклад, reduce , але працює в низхідному, а не висхідному порядку)

2. Використовуйте простий for циклу

Іноді старі способи є кращими:

 var index; var a = ["a", "b", "c"]; for (index = 0; index < a.length; ++index) { console.log(a[index]); } 

Якщо довжина масиву не буде змінюватися протягом циклу, і його продуктивність чутливого коду (малоймовірно), трохи більше складний варіант захоплення довжиною фронту може бути трохи - трохи швидше:

 var index, len; var a = ["a", "b", "c"]; for (index = 0, len = a.length; index < len; ++index) { console.log(a[index]); } 

І / або вважаючи назад:

 var index; var a = ["a", "b", "c"]; for (index = a.length - 1; index >= 0; --index) { console.log(a[index]); } 

Але з сучасними механізмами JavaScript вам рідко доводиться викопувати цей останній сік.

У ES2015 і вище ви можете зробити свій індекс і змінні значень локальними for циклу for :

 let a = ["a", "b", "c"]; for (let index = 0; index < a.length; ++index) { let value = a[index]; } //console.log(index); // Would cause "ReferenceError: index is not defined" //console.log(value); // Would cause "ReferenceError: value is not defined" 

І коли ви це робите, не тільки value а й index відтворюються для кожної ітерації циклу, тобто замикання, створені в тезі циклу, містять посилання на indexvalue ), створений для цієї конкретної ітерації:

 let divs = document.querySelectorAll("div"); for (let index = 0; index < divs.length; ++index) { divs[index].addEventListener('click', e => { alert("Index is: " + index); }); } 

Якщо у вас було п'ять div, ви б отримали "Index is: 0", якщо ви натиснули перший і "Index is: 4", якщо ви натиснули останній. Це не працює, якщо ви використовуєте var замість let .

3. Правильно використовуйте for-in

Ви отримаєте люди, які розповідають вам використовувати for-in , але це не те , що for-in для . for-in запишуться через перелічуваних властивості об'єкта, а не індекси масиву. Замовлення не гарантується, навіть в ES2015 (ES6). ES2015 робить визначити порядок властивостей об'єкта (за допомогою [[OwnPropertyKeys]] , [[Enumerate]] , і речі, які використовують їх як Object.getOwnPropertyKeys ), але він не визначає, що for-in буде слідувати, що замовлення. (Подробиці в цій другій відповіді .)

Тим не менш, це може бути корисно, особливо для розріджених масивів , якщо ви використовуєте відповідні запобіжні заходи:

 // 'a' is a sparse array var key; var a = []; a[0] = "a"; a[10] = "b"; a[10000] = "c"; for (key in a) { if (a.hasOwnProperty(key)  // These are explained /^0$|^[1-9]\d*$/.test(key)  // and then hidden key <= 4294967294 // away below ) { console.log(a[key]); } } 

Зверніть увагу на дві перевірки:

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

  2. Те, що ключ являє собою числову рядок base-10 в своїй звичайній строкової формі, і її значення <= 2 ^ 32 - 2 (що становить 4 294 967 294). Звідки це число? Це частина визначення індексу масиву в специфікації . Інші числа (нецілі числа, негативні числа, числа більше 2 ^ 32 - 2) не є індексами масиву. Причиною 2 ^ 32 - 2 є те, що робить найбільше значення індексу менше 2 ^ 32 - 1, що є максимальним значенням length масиву. (Наприклад, довжина масиву відповідає 32-розрядному цілому числу без знака.) (Підтвердження на RobG для вказівки в коментарі до повідомлення в блозі, що мій попередній тест був не зовсім правий).

Це крихітний біт додаткових накладних витрат для кожної ітерації циклів на більшості масивів, але якщо у вас є розріджений масив, це може бути більш ефективним способом для циклу, тому що це тільки цикли для записів, які насправді існують. Наприклад, для масиву вище, ми зациклюватися три рази (для клавіш "0" , "10" і "10000" - пам'ятаєте, це рядки), а не 10 001 разів.

Тепер ви не захочете писати це кожен раз, так що ви можете помістити це в свій інструментарій:

 function arrayHasOwnIndex(array, prop) { return array.hasOwnProperty(prop)  /^0$|^[1-9]\d*$/.test(prop)  prop <= 4294967294; // 2^32 - 2 } 

І тоді ми будемо використовувати його так:

 for (key in a) { if (arrayHasOwnIndex(a, key)) { console.log(a[key]); } } 

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

 for (key in a) { // "Good enough" for most cases if (String(parseInt(key, 10)) === key  a.hasOwnProperty(key)) { console.log(a[key]); } } 

4. Використовуйте for-of (використовуйте итератор неявно) (ES2015 +)

ES2015 додає ітератори до JavaScript. Найпростіший спосіб використання ітераторів - це новий оператор for-of . Це виглядає так:

 var val; var a = ["a", "b", "c"]; for (val of a) { console.log(val); } 

висновок:

 a b c

Під обкладинками, який отримує итератор з масиву і проходить через нього, отримує значення з нього. У цього немає проблеми, пов'язаної з використанням for-in , оскільки він використовує итератор, певний об'єктом (масивом), а масиви визначають, що їх ітератори повторюють свої записи (а не їх властивості). На відміну від for-in в ES5, порядок, в якому проглядаються записи, являє собою числовий порядок їх індексів.

5. Використовуйте итератор явно (ES2015 +)

Іноді ви можете явно використовувати итератор. Ви теж можете це зробити, хоча це набагато clunkier ніж for-of . Це виглядає так:

 var a = ["a", "b", "c"]; var it = a.values(); var entry; while (!(entry = it.next()).done) { console.log(entry.value); } 

Итератор - це об'єкт, що відповідає визначенню Iterator в специфікації. Його next метод повертає новий об'єкт результату кожен раз, коли ви його викликаєте. Об'єкт result має властивість, done , повідомляє нам, чи було це зроблено, і value властивості зі значенням для цієї ітерації. ( done необов'язково, якщо воно було б false , value є необов'язковим, якщо воно не undefined ).

Значення value варіюється в залежності від ітератора; масиви підтримують (принаймні) три функції, які повертають ітератори:

  • values() : Це той, який я використовував вище. Він повертає ітератор, де кожне value являє собою запис масиву для цієї ітерації ( "a" , "b" і "c" в прикладі раніше).
  • keys() : Повертає ітератор, де кожне value є ключовим для цієї ітерації (так що для нашої вище, що буде a "0" , а потім "1" , а потім "2" ).
  • entries() : повертає ітератор, де кожне value являє собою масив в формі [key, value] для цієї ітерації.

Для об'єктів, подібних масиву

Крім справжніх масивів, існують також подібні масиву об'єкти, які мають властивість length і властивості з числовими іменами: екземпляри NodeList , об'єкт arguments і т.д. Як ми NodeList їх вміст?

Використовуйте будь-який з перерахованих вище параметрів для масивів

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

  1. Використовувати forEach і пов'язані з ним (ES5 +)

    Різні функції на Array.prototype є "навмисно генеричними" і зазвичай можуть використовуватися для об'єктів, подібних масиву, за допомогою Function#call Function#apply або Function#apply . (Див. Застереження для об'єктів, наданих хостом, в кінці цієї відповіді, але це рідкісна проблема.)

    Припустимо, ви хотіли використовувати forEach в childNodes Node childNodes . Ви зробили б це:

     Array.prototype.forEach.call(node.childNodes, function(child) { // Do something with 'child' }); 

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

     // (This is all presumably in some scoping function) var forEach = Array.prototype.forEach; // Then later... forEach.call(node.childNodes, function(child) { // Do something with 'child' }); 
  2. Використовуйте простий for циклу

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

  3. Правильно використовувати for-in

    for-in з тими ж гарантіями, що і з масивом, повинен працювати і з подібними масиву об'єктами; може знадобитися застереження для об'єктів, наданих хостом, на № 1 вище.

  4. Використовувати for-of (використовувати итератор неявно) (ES2015 +)

    for-of буде використовувати итератор, що надається об'єктом (якщо такий є); ми повинні будемо побачити, як це відбувається з різними подібними масивом об'єктами, особливо з хост файлами. Наприклад, специфікація для NodeList від querySelectorAll була оновлена для підтримки ітерації. Специфікацію для HTMLCollection з getElementsByTagName не було.

  5. Використовувати итератор явно (ES2015 +)

    Див. № 4, нам потрібно подивитися, як розгортаються ітератори.

Створити справжній масив

В інших випадках ви можете перетворити об'єкт, подібний масиву, в справжній масив. Робити це напрочуд легко:

  1. Використовувати метод slice масивів

    Ми можемо використовувати метод slice масивів, який, як і інші методи, згадані вище, є "навмисно загальним" і тому може використовуватися з подібними масиву об'єктами, наприклад:

     var trueArray = Array.prototype.slice.call(arrayLikeObject); 

    Так, наприклад, якщо ми хочемо перетворити NodeList в справжній масив, ми могли б зробити це:

     var divs = Array.prototype.slice.call(document.querySelectorAll("div")); 

    Див. "Застереження для об'єктів, що надаються хостом" нижче. Зокрема, зверніть увагу, що це не вдасться в IE8 і раніше, що не дозволяє використовувати об'єкти, надані хостом, як this .

  2. Використовувати синтаксис поширення ( ... )

    Також можливо використовувати синтаксис поширення ES2015 з механізмами JavaScript, які підтримують цю функцію:

     var trueArray = [...iterableObject]; 

    Так, наприклад, якщо ми хочемо перетворити NodeList в істинний масив, з синтаксисом поширення це стає досить коротким:

     var divs = [...document.querySelectorAll("div")]; 
  3. Використовуйте Array.from (spec) | (MDN)

    Array.from (ES2015 +, але легко поли заповнений) створює масив з об'єкта, подібного масиву, при необхідності спочатку передаючи записи через функцію зіставлення. так:

     var divs = Array.from(document.querySelectorAll("div")); 

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

     // Arrow function (ES2015): var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName); // Standard function (since 'Array.from' can be shimmed): var divs = Array.from(document.querySelectorAll(".some-class"), function(element) { return element.tagName; }); 

Застереження для об'єктів, що надаються хостом

Якщо ви використовуєте функції Array.prototype з об'єктами, подібними хостам (списки DOM і інші речі, що надаються браузером, а не двигуном JavaScript), ви повинні бути впевнені, що будете тестувати в своїх цільових середовищах, щоб переконатися, що наданий хост об'єкт поводиться правильно. Більшість з них поводяться правильно (зараз), але важливо перевірити. Причина в тому, що більшість методів Array.prototype які ви, ймовірно, захочете використовувати, покладаються на наданий хостом об'єкт, даючи чесну відповідь на абстрактну [[HasProperty]] . На момент написання цієї статті браузери дуже добре впоралися з цим, але специфікація 5.1 дозволила припустити, що об'єкт, що надається хостом, може бути нечесним. Це в §8.6.2 , кілька абзаців нижче великої таблиці на початку цього розділу), де йдеться:

Хост-об'єкти можуть реалізовувати ці внутрішні методи будь-яким способом, якщо не вказано інше; наприклад, одна можливість полягає в тому, що [[Get]] і [[Put]] для певного об'єкта хоста дійсно витягують і зберігають значення властивостей, але [[HasProperty]] завжди генерують false.

(Я не міг знайти еквівалентну формулювання в ES2015 специфікації, але він зобов'язаний ще бути.) Знову ж таки, як це написанням загальних хоста наданого масиву типу об'єктів в сучасних браузерах [ NodeList примірників, наприклад] зробити ручка [[HasProperty]] правильно, але важливо перевірити.)

6292
17 февр. відповідь дан TJ Crowder 17 февр. 2012-02-17 16:53 '12 о 16:53 2012-02-17 16:53

Змінити: ця відповідь безнадійно застарів. Для більш сучасного підходу подивіться на методи, доступні в масиві . Цікавлять методи можуть бути:

  • для кожного
  • мапа
  • фільтр
  • застібка-блискавка
  • зменшити
  • кожен
  • трохи

Стандартний спосіб ітерації масиву в JavaScript - це ваніль for -loop:

border=0
 var length = arr.length, element = null; for (var i = 0; i < length; i++) { element = arr[i]; // Do something with element } 

Зверніть увагу, однак, що цей підхід хороший, тільки якщо у вас щільний масив, і кожен індекс зайнятий елементом. Якщо масив розріджений, то при такому підході ви можете зіткнутися з проблемами продуктивності, так як ви будете перебирати безліч індексів, які насправді не існують в масиві. В цьому випадку краще використовувати for.. in -loop. Однак ви повинні використовувати відповідні запобіжні заходи, щоб гарантувати, що будуть діяти тільки необхідні властивості масиву (тобто елементи масиву), оскільки for..in -loop також буде перераховуватися в застарілих браузерах, або якщо додаткові властивості визначені як enumerable .

У ECMAScript 5 для прототипу масиву буде використовуватися метод forEach, але він не підтримується в застарілих браузерах. Тому, щоб мати можливість використовувати його послідовно, ви повинні або мати середовище, яке його підтримує (наприклад, Node.js для серверного JavaScript), або використовувати "Polyfill". Polyfill для цієї функціональності, проте, тривіальний, і, оскільки він робить код більш легким для читання, його варто включити в Polyfill.

470
17 февр. відповідь дан PatrikAkerstrand 17 февр. 2012-02-17 16:55 '12 о 16:55 2012-02-17 16:55

Якщо ви використовуєте бібліотеку jQuery , ви можете використовувати jQuery.each :

 var length = yourArray.length; for (var i = 0; i < length; i++) { // Do something with yourArray[i]. } 
214
17 февр. відповідь дан Poonam 17 февр. 2012-02-17 17:01 '12 о 17:01 2012-02-17 17:01

цикл назад

Я думаю, що зворотне для циклу заслуговує згадки тут:

 for (var i = array.length; i--; ) { // process array[i] } 

переваги:

  • Вам не потрібно оголошувати тимчасову змінну len або порівнювати її з array.length на кожній ітерації, яка може бути хвилинної оптимізацією.
  • Видалення братів / сестер з DOM в зворотному порядку зазвичай більш ефективно. (Браузеру необхідно менше переміщати елементи у внутрішніх масивах.)
  • Якщо ви змінюєте масив під час циклу, в або після індексу я (наприклад, ви видаляєте або вставляєте елемент в array[i] ), тоді прямий цикл пропускає елемент, який зміщений вліво в положення я або перевірити ще раз i-й елемент, який був зміщений вправо. У традиційному циклі ви можете оновити i, щоб вказати на наступний елемент, який потребує обробки - 1, але просто змінити напрямок ітерації часто простіше і більш елегантне рішення .
  • Аналогічно, при зміні або видаленні вкладених елементів DOM обробка в зворотному порядку може обходити помилки. Наприклад, розгляньте можливість зміни innerHTML батьківського node перед обробкою його дочірніх елементів. До моменту досягнення дочірнього елемента node він буде відділений від DOM, замінивши його новим нащадком, коли був записаний батьківський innerHTML.
  • коротше, і прочитати, ніж деякі інші доступні опції. Хоча він втрачає до forEach() і до ES6 for ... of .

недоліки:

  • Він обробляє елементи в зворотному порядку. Якщо ви будували новий масив з результатів або друкували на екрані, природно висновок буде скасований щодо вихідного порядку.
  • Неоднократно вставляя братьев и сестер в DOM в качестве первого ребенка, чтобы сохранить их порядок, менее эффективен . (В браузере все равно придется перекладывать вещи.) Чтобы создавать узлы DOM эффективно и упорядоченно, просто переходите вперед и добавьте как обычно (а также используйте "фрагмент документа" ).
  • Обратный цикл запутывает для младших разработчиков. (Вы можете считать это преимущество, в зависимости от вашего прогноза.)

Должен ли я всегда использовать его?