# Все способы перебора массива в JavaScript

{% embed url="https://habr.com/ru/articles/247857/" %}

**Содержание:**



*   I. Перебор настоящих массивов\


    1. Метод forEach и родственные методы
    2. Цикл for
    3. Правильное использование цикла for...in
    4. Цикл for...of (неявное использование итератора)
    5. Явное использование итератора

    \

*   II. Перебор массивоподобных объектов\


    1. Использование способов перебора настоящих массивов
    2. Преобразование в настоящий массив
    3. Замечание по объектам среды исполнения

    \


#### I. Перебор настоящих массивов

\
На данный момент есть три способа перебора элементов настоящего массива:\


1. метод `Array.prototype.forEach`;
2. классический цикл `for`;
3. «правильно» построенный цикл `for...in`.

\
Кроме того, в скором времени, с появлением нового стандарта ECMAScript 6 (ES 6), ожидается еще два способа:\


1. цикл `for...of` (неявное использование итератора);
2. явное использование итератора.

\


**1. Метод forEach и родственные методы**

\
Если ваш проект рассчитан на поддержку возможностей стандарта ECMAScript 5 (ES5), вы можете использовать одно из его нововведений — метод [forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18).\
\
Пример использования:\


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

\
В общем случае использование `forEach` требует подключения библиотеки эмуляции [es5-shim](https://github.com/es-shims/es5-shim) для браузеров, не имеющих нативной поддержки этого метода. К ним относятся IE 8 и более ранние версии, которые до сих пор кое-где еще используются.\
\
К достоинствам `forEach` относится то, что здесь не нужно объявлять локальные переменные для хранения индекса и значения текущего элемента массива, поскольку они автоматически передаются в функцию обратного вызова (колбек) в качестве аргументов.\
\
Если вас беспокоят возможные затраты на вызов колбека для каждого элемента, не волнуйтесь и прочитайте [это](http://blog.niftysnippets.org/2012/02/foreach-and-runtime-cost.html).\
\
`forEach` предназначен для перебора всех элементов массива, но кроме него ES5 предлагает еще несколько полезных методов для перебора всех или некоторых элементов плюс выполнения при этом каких-либо действий с ними:\


* `every` — возвращает `true`, если для каждого элемента массива колбек возвращает значение приводимое к `true`.
* `some` — возвращает `true`, если хотя бы для одного элемента массива колбек возвращает значение приводимое к `true`.
* `filter` — создает новый массив, включающий те элементы исходного массива, для которых колбек возвращает `true`.
* `map` — создает новый массив, состоящий из значений возращаемых колбеком.
* `reduce` — сводит массив к единственному значению, применяя колбек по очереди к каждому элементу массива, начиная с первого (может быть полезен для вычисления суммы элементов массива и других итоговых функций).
* `reduceRight` — работает аналогично reduce, но перебирает элементы в обратном порядке.

\


**2. Цикл for**

\
&#xNAN;_&#x421;тарый добрый `for` рулит_:\
\


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

\
Если длина массива неизменна в течение всего цикла, а сам цикл принадлежит критическому в плане производительности участку кода (что маловероятно), то можно использовать «более оптимальную» версию `for` с хранением длины массива:\
\


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

\
Теоретически этот код должен выполняться чуть быстрее, чем предыдущий.\
\
Если порядок перебора элементов не важен, то можно пойти еще дальше в плане оптимизации и избавиться от переменной для хранения длины массива, изменив порядок перебора на обратный:\
\


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

\
Тем не менее, в современных движках JavaScript подобные игры с оптимизацией обычно ничего не значат.\
\


**3. Правильное использование цикла for...in**

\
Если вам посоветуют использовать цикл `for...in`, помните, что [перебор массивов — не то, для чего он предназначен](http://blog.niftysnippets.org/2010/11/myths-and-realities-of-forin.html). Вопреки распространенному заблуждению цикл `for...in` перебирает не индексы массива, а перечислимые свойства объекта.\
\
Тем не менее, в некоторых случаях, таких как перебор [разреженных массивов](http://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D0%B7%D1%80%D0%B5%D0%B6%D1%91%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2), `for...in` может оказаться полезным, если только соблюдать при этом меры предосторожности, как показано в примере ниже:\
\


```
// a - разреженный массив
var a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (var key in a) {
    if (a.hasOwnProperty(key) &&
        /^0$|^[1-9]\d*$/.test(key) &&
        key <= 4294967294) {
        console.log(a[key]);
    }
}
```

\
В данном примере на каждой итерации цикла выполняется две проверки:\


1. то, что массив имеет собственное свойство с именем `key` (не наследованное из его прототипа).
2. то, что `key` — строка, содержащая десятичную запись целого числа, значение которого меньше `4294967294`. Откуда берется последнее число? Из [определения](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4) индекса массива в ES5, из которого следует, что наибольший индекс, который может иметь элемент в массиве: `(2^32 - 2) = 4294967294`.

\
Конечно, такие проверки отнимут лишнее время при выполнении цикла. Но в случае разреженного массива этот способ более эффективен, чем цикл `for`, поскольку в этом случае перебираются только те элементы, которые явно определены в массиве. Так, в примере выше будет выполнено всего 3 итерации (для индексов 0, 10 и 10000) — против 10001 в цикле `for`.\
\
Чтобы не писать такой громоздкий код проверок каждый раз, когда требуется перебор массива, можно оформить его в виде отдельной функции:\
\


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

\
Тогда тело цикла из примера значительно сократится:\
\


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

\
Рассмотренный выше код проверок является универсальным, подходящим для всех случаев. Но вместо него можно использовать более короткую версию, хотя формально и не совсем правильную, но, тем не менее, подходящую для большинства случаев:\
\


```
for (key in a) {
    if (a.hasOwnProperty(key) && String(parseInt(key, 10)) === key) {
        console.log(a[key]);
    }
}
```

\


**4. Цикл for...of (неявное использование итератора)**

\
ES6, пока все еще пребывающий в статусе [черновика](http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts), должен ввести в JavaScript итераторы.\
\
&#xNAN;_&#x418;тератор_ — это реализуемый объектом протокол, который определяет стандартный способ получения последовательности значений (конечной или бесконечной).\
Итератор — это объект, в котором определен метод `next()` — функция без аргументов, возвращающая объект с двумя свойствами:\


1. `done` (`boolean`) — принимает значение `true`, если итератор достиг конца итерируемой последовательности. В противном случае имеет значение `false`.
2. `value` — определяет значение, возвращаемое итератором. Может быть не определено (отсутствовать), если свойство `done` имеет значение `true`.

\
Многие встроенные объекты, в т.ч. настоящие массивы, имеют итераторы по умолчанию. Простейший способ применения итератора в настоящих массивах — использовать новую конструкцию `for...of`.\
\
Пример использования `for...of`:\
\


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

\
В приведенном примере цикл `for...of` неявно вызывает итератор объекта Array для получения каждого значения массива.\
\


**5. Явное использование итератора**

\
Итераторы можно также использовать и явно, правда, в этом случае код становится значительно сложнее, по сравнению с циклом `for...of`. Выглядит это примерно так:\
\


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

\
В данном примере метод `Array.prototype.entries` возвращает итератор, который используется для вывода значений массива. На каждой итерации `entry.value` содержит массив вида `[ключ, значение]`.\
\


#### II. Перебор массивоподобных объектов

\
Кроме настоящих массивов, в JavaScript встречаются также _массивоподобные объекты_. С настоящими массивами их роднит то, что они имеют свойство `length` и свойства с именами в виде чисел, соответствующие элементам массива. В качестве примеров можно назвать DOM коллекции `NodeList` и псевдомассив `arguments`, доступный внутри любой функции/метода.\
\


**1. Использование способов перебора настоящих массивов**

\
Как минимум большинство, если не все, способы перебора настоящих массивов могут быть применены для перебора массивоподобных объектов.\
\
Конструкции `for` и `for...in` могут быть применены к массивоподобным объектам точно тем же путем, что и к настоящим массивам.\
\
`forEach` и другие методы `Array.prototype` также применимы к массивоподобным объектам. Для этого нужно использовать вызов [Function.call](http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4.4) или [Function.apply](http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4.3).\
\
Например, если вы хотите применить `forEach` к свойству `childNodes` объекта `Node`, то это делается так:\
\


```
Array.prototype.forEach.call(node.childNodes, function(child) {
    // делаем что-нибудь с объектом child
});
```

\
Для удобства повторного использования этого приема, можно объявить ссылку на метод `Array.prototype.forEach` в отдельной переменной и использовать ее как сокращение:\
\


```
// (Предполагается, что весь код ниже находится в одной области видимости)
var forEach = Array.prototype.forEach;

// ...

forEach.call(node.childNodes, function(child) {
    // делаем что-нибудь с объектом child
});
```

\
Если в массивоподобном объекте имеется итератор, то его можно использовать явно или неявно для перебора объекта таким же способом, как и для настоящих массивов.\
\


**2. Преобразование в настоящий массив**

\
Есть также еще один, очень простой, способ перебора массивоподобного объекта: преобразовать его в настоящий массив и использовать любой из рассмотренных выше способов перебора настоящих массивов. Для преобразования можно использовать универсальный метод `Array.prototype.slice`, который может быть применен к любому массивоподобному объекту. Делается это очень просто, как показано в примере ниже:\
\


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

\
Например, если вы хотите преобразовать коллекцию `NodeList` в настоящий массив, вам нужен примерно такой код:\
\


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

\
Update: Как было отмечено в комментариях [rock](https://habrahabr.ru/users/rock/) и [torbasow](https://habrahabr.ru/users/torbasow/), в ES6 вместо `Array.prototype.slice` можно использовать более наглядный метод [`Array.from`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from).\
\


**3. Замечание по объектам среды исполнения**

\
Если вы применяете методы `Array.prototype` к объектам среды исполнения (таких как DOM коллекции), то вы должны иметь в виду, что правильная работа этих методов не гарантирована во всех средах исполнения (в т.ч. в браузерах). Это зависит от поведения конкретного объекта в конкретной среде исполнения, если точнее, от того, как в этом объекте реализована абстрактная операция `HasProperty`. Проблема в том, что сам стандарт ES5 допускает возможность неправильного поведения объекта по отношению к этой операции (см. [§8.6.2](http://www.ecma-international.org/ecma-262/5.1/#sec-8.6.2)).\
\
Поэтому важно тестировать работу методов `Array.prototype` в каждой среде исполнения (браузере), в которой планируется использование вашего приложения.
