Все способы перебора массива в JavaScript
Содержание:
I. Перебор настоящих массивов
Метод forEach и родственные методы
Цикл for
Правильное использование цикла for...in
Цикл for...of (неявное использование итератора)
Явное использование итератора
II. Перебор массивоподобных объектов
Использование способов перебора настоящих массивов
Преобразование в настоящий массив
Замечание по объектам среды исполнения
I. Перебор настоящих массивов
На данный момент есть три способа перебора элементов настоящего массива:
метод
Array.prototype.forEach
;классический цикл
for
;«правильно» построенный цикл
for...in
.
Кроме того, в скором времени, с появлением нового стандарта ECMAScript 6 (ES 6), ожидается еще два способа:
цикл
for...of
(неявное использование итератора);явное использование итератора.
1. Метод forEach и родственные методы
Если ваш проект рассчитан на поддержку возможностей стандарта ECMAScript 5 (ES5), вы можете использовать одно из его нововведений — метод forEach. Пример использования:
В общем случае использование forEach
требует подключения библиотеки эмуляции es5-shim для браузеров, не имеющих нативной поддержки этого метода. К ним относятся IE 8 и более ранние версии, которые до сих пор кое-где еще используются.
К достоинствам forEach
относится то, что здесь не нужно объявлять локальные переменные для хранения индекса и значения текущего элемента массива, поскольку они автоматически передаются в функцию обратного вызова (колбек) в качестве аргументов.
Если вас беспокоят возможные затраты на вызов колбека для каждого элемента, не волнуйтесь и прочитайте это.
forEach
предназначен для перебора всех элементов массива, но кроме него ES5 предлагает еще несколько полезных методов для перебора всех или некоторых элементов плюс выполнения при этом каких-либо действий с ними:
every
— возвращаетtrue
, если для каждого элемента массива колбек возвращает значение приводимое кtrue
.some
— возвращаетtrue
, если хотя бы для одного элемента массива колбек возвращает значение приводимое кtrue
.filter
— создает новый массив, включающий те элементы исходного массива, для которых колбек возвращаетtrue
.map
— создает новый массив, состоящий из значений возращаемых колбеком.reduce
— сводит массив к единственному значению, применяя колбек по очереди к каждому элементу массива, начиная с первого (может быть полезен для вычисления суммы элементов массива и других итоговых функций).reduceRight
— работает аналогично reduce, но перебирает элементы в обратном порядке.
2. Цикл for
Старый добрый for
рулит:
Если длина массива неизменна в течение всего цикла, а сам цикл принадлежит критическому в плане производительности участку кода (что маловероятно), то можно использовать «более оптимальную» версию for
с хранением длины массива:
Теоретически этот код должен выполняться чуть быстрее, чем предыдущий. Если порядок перебора элементов не важен, то можно пойти еще дальше в плане оптимизации и избавиться от переменной для хранения длины массива, изменив порядок перебора на обратный:
Тем не менее, в современных движках JavaScript подобные игры с оптимизацией обычно ничего не значат.
3. Правильное использование цикла for...in
Если вам посоветуют использовать цикл for...in
, помните, что перебор массивов — не то, для чего он предназначен. Вопреки распространенному заблуждению цикл for...in
перебирает не индексы массива, а перечислимые свойства объекта.
Тем не менее, в некоторых случаях, таких как перебор разреженных массивов, for...in
может оказаться полезным, если только соблюдать при этом меры предосторожности, как показано в примере ниже:
В данном примере на каждой итерации цикла выполняется две проверки:
то, что массив имеет собственное свойство с именем
key
(не наследованное из его прототипа).то, что
key
— строка, содержащая десятичную запись целого числа, значение которого меньше4294967294
. Откуда берется последнее число? Из определения индекса массива в ES5, из которого следует, что наибольший индекс, который может иметь элемент в массиве:(2^32 - 2) = 4294967294
.
Конечно, такие проверки отнимут лишнее время при выполнении цикла. Но в случае разреженного массива этот способ более эффективен, чем цикл for
, поскольку в этом случае перебираются только те элементы, которые явно определены в массиве. Так, в примере выше будет выполнено всего 3 итерации (для индексов 0, 10 и 10000) — против 10001 в цикле for
.
Чтобы не писать такой громоздкий код проверок каждый раз, когда требуется перебор массива, можно оформить его в виде отдельной функции:
Тогда тело цикла из примера значительно сократится:
Рассмотренный выше код проверок является универсальным, подходящим для всех случаев. Но вместо него можно использовать более короткую версию, хотя формально и не совсем правильную, но, тем не менее, подходящую для большинства случаев:
4. Цикл for...of (неявное использование итератора)
ES6, пока все еще пребывающий в статусе черновика, должен ввести в JavaScript итераторы.
Итератор — это реализуемый объектом протокол, который определяет стандартный способ получения последовательности значений (конечной или бесконечной).
Итератор — это объект, в котором определен метод next()
— функция без аргументов, возвращающая объект с двумя свойствами:
done
(boolean
) — принимает значениеtrue
, если итератор достиг конца итерируемой последовательности. В противном случае имеет значениеfalse
.value
— определяет значение, возвращаемое итератором. Может быть не определено (отсутствовать), если свойствоdone
имеет значениеtrue
.
Многие встроенные объекты, в т.ч. настоящие массивы, имеют итераторы по умолчанию. Простейший способ применения итератора в настоящих массивах — использовать новую конструкцию for...of
.
Пример использования for...of
:
В приведенном примере цикл for...of
неявно вызывает итератор объекта Array для получения каждого значения массива.
5. Явное использование итератора
Итераторы можно также использовать и явно, правда, в этом случае код становится значительно сложнее, по сравнению с циклом for...of
. Выглядит это примерно так:
В данном примере метод Array.prototype.entries
возвращает итератор, который используется для вывода значений массива. На каждой итерации entry.value
содержит массив вида [ключ, значение]
.
II. Перебор массивоподобных объектов
Кроме настоящих массивов, в JavaScript встречаются также массивоподобные объекты. С настоящими массивами их роднит то, что они имеют свойство length
и свойства с именами в виде чисел, соответствующие элементам массива. В качестве примеров можно назвать DOM коллекции NodeList
и псевдомассив arguments
, доступный внутри любой функции/метода.
1. Использование способов перебора настоящих массивов
Как минимум большинство, если не все, способы перебора настоящих массивов могут быть применены для перебора массивоподобных объектов.
Конструкции for
и for...in
могут быть применены к массивоподобным объектам точно тем же путем, что и к настоящим массивам.
forEach
и другие методы Array.prototype
также применимы к массивоподобным объектам. Для этого нужно использовать вызов Function.call или Function.apply.
Например, если вы хотите применить forEach
к свойству childNodes
объекта Node
, то это делается так:
Для удобства повторного использования этого приема, можно объявить ссылку на метод Array.prototype.forEach
в отдельной переменной и использовать ее как сокращение:
Если в массивоподобном объекте имеется итератор, то его можно использовать явно или неявно для перебора объекта таким же способом, как и для настоящих массивов.
2. Преобразование в настоящий массив
Есть также еще один, очень простой, способ перебора массивоподобного объекта: преобразовать его в настоящий массив и использовать любой из рассмотренных выше способов перебора настоящих массивов. Для преобразования можно использовать универсальный метод Array.prototype.slice
, который может быть применен к любому массивоподобному объекту. Делается это очень просто, как показано в примере ниже:
Например, если вы хотите преобразовать коллекцию NodeList
в настоящий массив, вам нужен примерно такой код:
Update: Как было отмечено в комментариях rock и torbasow, в ES6 вместо Array.prototype.slice
можно использовать более наглядный метод Array.from
.
3. Замечание по объектам среды исполнения
Если вы применяете методы Array.prototype
к объектам среды исполнения (таких как DOM коллекции), то вы должны иметь в виду, что правильная работа этих методов не гарантирована во всех средах исполнения (в т.ч. в браузерах). Это зависит от поведения конкретного объекта в конкретной среде исполнения, если точнее, от того, как в этом объекте реализована абстрактная операция HasProperty
. Проблема в том, что сам стандарт ES5 допускает возможность неправильного поведения объекта по отношению к этой операции (см. §8.6.2).
Поэтому важно тестировать работу методов Array.prototype
в каждой среде исполнения (браузере), в которой планируется использование вашего приложения.
Last updated