For-each над масив в JavaScript?

Как мога да направя цикъл през всички записи в масив с помощта на JavaScript?

Мислех, че е нещо подобно:

forEach(instance in theArray)

Където theArray е моят масив, но това изглежда е неправилно.

Решение

TL;DR

  • Не'използвайте for-in, освен ако не го използвате с предпазни мерки или поне не сте наясно защо може да ви ухапе.
  • Най-добрите ви залози обикновено са
  • цикъл for-of (само в ES2015+),
  • Array#forEach ([spec][1] | [MDN][2]) (или неговите роднини some и подобни) (само ES5+),
  • обикновен старомоден цикъл for,
  • или for-in с предпазни мерки. Но има още много неща, които да изследвате, четете нататък...

    JavaScript разполага с мощна семантика за циклиране през масиви и масивоподобни обекти. Разделих отговора на две части: Опции за истински масиви и опции за неща, които са просто масивоподобни, като например обект arguments, други итерабилни обекти (ES2015+), колекции DOM и т.н. Набързо ще отбележа, че можете да използвате опциите на ES2015 сега, дори и на двигатели ES5, чрез транспириране* на ES2015 към ES5. За повече информация потърсете "ES2015 transpiling" / "ES6 transpiling"... Добре, нека разгледаме нашите възможности:

    За действителни масиви

    Имате три опции в [ECMAScript 5][3] ("ES5"), най-широко поддържаната версия в момента, и още две, добавени в [ECMAScript 2015][4] ("ES2015", "ES6"):

  1. Използване на forEach и свързаните с него (ES5+)
  2. Използване на прост цикъл for
  3. Използвайте for-in правилно
  4. Използвайте for-of (използвайте итератор по подразбиране) (ES2015+)
  5. Използвайте итератор явно (ES2015+) Подробности:

    1. Използвайте forEach и свързаните с него

    В която и да е мъглява съвременна среда (т.е. не в IE8), където имате достъп до функциите Array, добавени от ES5 (директно или с помощта на polyfills), можете да използвате forEach ([spec][1] | [MDN][2]):

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 дефинира няколко други полезни "work your way through the array and do things" функции, включително:

  • [every][5] (спира цикъла при първия път, когато обратната връзка върне false или нещо невярно)
  • [some][6] (спира цикъла при първия път, когато обратната връзка върне true или нещо вярно)
  • [filter][7] (създава нов масив, включващ елементите, при които функцията за филтриране връща true, и пропускащ тези, при които връща false)
  • [map][8] (създава нов масив от стойностите, върнати от обратната функция)
  • [reduce][9] (изгражда стойност чрез многократно извикване на обратната връзка, като предава предишни стойности; вижте спецификацията за подробности; полезно за сумиране на съдържанието на масив и много други неща)
  • [reduceRight][10] (подобно на 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 и по-нови версии можете да направите променливите index и value локални за цикъла for:

let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
    let value = a[index];
    console.log(index, value);
}
//console.log(index);   // would cause "ReferenceError: index is not defined"
//console.log(value);   // would cause "ReferenceError: value is not defined"
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
    let value = a[index];
    console.log(index, value);
}
try {
    console.log(index);
} catch (e) {
    console.error(e);   // "ReferenceError: index is not defined"
}
try {
    console.log(value);
} catch (e) {
    console.error(e);   // "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 => {
        console.log("Index is: " + index);
    });
}
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
    divs[index].addEventListener('click', e => {
        console.log("Index is: " + index);
    });
}
<div>zero</div>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>

Ако имахте пет div-а, щяхте да получите "Index is: 0" ако щракнете върху първия и "Индексът е: 4", ако щракнете върху последния. Това не работи, ако използвате var вместо let.

3. Използвайте for-in правилно

Хората ще ви кажат да използвате for-in, но for-in не служи за това [11]. for-in преминава през изброимите свойства на обекта, а не през индексите на масив. Поредността не е гарантирана, дори в ES2015 (ES6). ES2015+ определя ред за свойствата на обектите (чрез [[OwnPropertyKeys]], [[Enumerate]] и неща, които ги използват, като [Object.getOwnPropertyKeys][12]), но не определя, че for-in ще следва този ред. (Подробности в [този друг отговор][13].) Единствените реални случаи на използване на for-in върху масив са:

  • Това е [рядък масив][14] с огромни празнини в него, или
  • Използвате свойства, които не са елементи, и искате да ги включите в цикъла Разглеждаме само първия пример: Можете да използвате 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 checks are
        /^0$|^[1-9]\d*$/.test(key) &&    // explained
        key 
Коментари (24)

Забележка: Този отговор е безнадеждно остарял. За по-съвременен подход вижте методите, достъпни за масив. Интересни методи могат да бъдат:

  • forEach
  • map
  • filter
  • zip
  • намаляване
  • всеки
  • някои

Стандартният начин за итериране на масив в JavaScript е обикновеният цикъл for:

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

Имайте предвид обаче, че този подход е добър само ако имате плътен масив и всеки индекс е зает от елемент. Ако масивът е рядък, този подход може да доведе до проблеми с производителността, тъй като ще се итерира над много индекси, които не съществуват в масива. В този случай цикълът for .. in може да се окаже по-добра идея. Въпреки това трябва да използвате подходящи предпазни мерки, за да гарантирате, че се действа само върху желаните свойства на масива (т.е. елементите на масива), тъй като цикълът for..in ще бъде изброяван и в по-старите браузъри или ако допълнителните свойства са дефинирани като enumerable.

В ECMAScript 5 ще има метод forEach на прототипа на масива, но той не се поддържа в по-старите браузъри. Така че, за да можете да го използвате последователно, трябва или да имате среда, която го поддържа (например Node.js за сървърен JavaScript), или да използвате "Polyfill". Полифилът за тази функционалност обаче е тривиален и тъй като улеснява четенето на кода, е добре да бъде включен.

Коментари (4)

Ако искате да направите цикъл в масив, използвайте стандартния цикъл от три части for.

for (var i = 0; i < myArray.length; i++) {
    var arrayItem = myArray[i];
}

Можете да получите някои оптимизации на производителността, като кеширате myArray.length или го преглеждате в обратна посока.

Коментари (5)