Menu

Методы массивов в JavaScript: map, filter, reduce

Методы массивов, которые заменяют большинство циклов for: map, filter, reduce, find, some, every — и разбираемся, какие из них мутируют массив, а какие возвращают новый.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

У массивов есть свой набор инструментов

Массивы в JavaScript несут с собой внушительный арсенал встроенных методов. Практически всё, ради чего вы обычно пишете цикл for — преобразовать значения, отобрать нужные, посчитать сумму — уже реализовано в виде метода, который умещается в одну строку, читается лучше и легко комбинируется с другими.

Базовый набор — три метода: map, filter и reduce. Освойте их и ещё пару-тройку соседей, и ваш код, перегруженный циклами, превратится в нечто, что понятно с первого взгляда.

Каждый метод принимает колбэк и что-то возвращает. При этом ни один из них не изменил исходный nums — этот момент стоит запомнить с самого начала.

map: преобразование каждого элемента

map принимает функцию и вызывает её для каждого элемента, собирая возвращённые значения в новый массив той же длины. Используйте его, когда нужно получить «одно значение на входе — одно на выходе».

Колбэк вторым аргументом получает индекс — если он нужен: arr.map((item, i) => ...). Не нужен — просто не указывайте его.

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

filter: оставляем только то, что подходит

Метод filter вызывает предикат — функцию, возвращающую true или false — для каждого элемента и оставляет те, на которых она вернула истинное значение. Новый массив получится той же длины или короче.

map и filter отлично соединяются в цепочку. Читайте такую цепочку слева направо — как конвейер:

Сначала filter, потом map — так map отработает только по тем элементам, которые прошли отбор.

reduce: свёртка массива в одно значение

reduce — самый универсальный из этой тройки. Ему передают функцию-редьюсер вида (accumulator, item) => newAccumulator и стартовое значение. Он проходит по массиву, передавая в редьюсер каждый элемент вместе с текущим значением аккумулятора, и в итоге возвращает то, во что этот аккумулятор превратился.

Результат вовсе не обязан быть числом. Это может быть объект, другой массив, строка — словом, всё, что вы собираете по ходу дела:

Всегда передавайте начальное значение (второй аргумент). Без него reduce берёт в качестве стартового аккумулятора первый элемент массива — это ломается на пустых массивах и часто делает вовсе не то, что вы ожидали.

reduce — штука мощная, но когда логика усложняется, читать такой код становится больно. Если ваш редьюсер занимает больше пары строк, обычный цикл for...of зачастую нагляднее.

forEach: побочные эффекты без возврата значения

forEach — это по сути map без возвращаемого массива. Он нужен, когда с каждым элементом надо что-то сделать: вывести в консоль, дёрнуть API, обновить DOM — а новая коллекция при этом не требуется.

Два момента, которые стоит помнить:

  • forEach возвращает undefined. Прицепить после него .map() не получится.
  • Досрочно выйти из forEach через break нельзя. Если нужен ранний выход — берите for...of или some/every.

Если вы ловите себя на том, что пишете arr.forEach(x => results.push(transform(x))) — это же map.

find и findIndex: найти один элемент

find возвращает первый элемент, удовлетворяющий условию, либо undefined, если совпадений нет. findIndex возвращает индекс (или -1).

find останавливается на первом совпадении. Не используйте конструкцию filter(...)[0] — она пробежит по всему массиву, а потом просто выкинет всё лишнее.

some и every: булевы вопросы к массиву

some возвращает true, если хотя бы один элемент проходит проверку. every же возвращает true, только если условию удовлетворяют абсолютно все элементы.

Оба метода ленивые: some останавливается на первом true, а every — на первом false. Это правильный выбор, когда нужно ответить на вопрос «есть ли хотя бы один…» или «все ли…».

slice vs splice: копирование против вырезания

Названия похожи, но ведут себя эти методы совершенно по-разному.

slice(start, end) возвращает поверхностную копию части массива и ничего не меняет в оригинале. Индекс end не включается; если его не указать, копирование пойдёт до конца массива.

splice(start, deleteCount, ...items) изменяет сам массив — работает по месту. Он удаляет deleteCount элементов, начиная с индекса start, при желании вставляет на их место новые и возвращает массив удалённых элементов.

Мнемоника: slice — безопасен (копирует), splice — хирургически правит массив на месте.

Мутирующие и немутирующие методы массива в JavaScript

Эта разница — принципиальная. Код, который случайно мутирует общий массив, даёт один из самых неприятных багов, которые потом попробуй поймай.

Мутирующие (меняют оригинал, возвращают обычно что-то другое):

  • push, pop, shift, unshift
  • splice, sort, reverse
  • fill, copyWithin

Немутирующие (возвращают новый массив или значение, оригинал не трогают):

  • map, filter, slice, concat
  • flat, flatMap
  • find, findIndex, some, every, includes, indexOf
  • reduce, reduceRight

Отдельно стоит держать в голове sort и reverse — выглядят безобидно, а тихонько мутируют исходник. Если нужна отсортированная копия — сначала сделайте slice:

В современном JS появились неразрушающие аналоги: toSorted, toReversed, toSpliced и with. Они возвращают новый массив, не трогая исходный. Поддерживаются во всех актуальных рантаймах — так что смело берите их, если среда позволяет.

flat и flatMap

Метод flat «разглаживает» вложенные массивы на один уровень (или глубже, если передать аргумент глубины). А flatMap — это по сути map, за которым сразу идёт flat на один уровень; он удобен, когда из каждого элемента нужно получить ноль, один или несколько результатов.

flatMap — это аккуратный способ «развернуть» элементы (из одного на входе получить несколько на выходе), не вызывая потом ещё и flat().

Собираем всё вместе

Небольшой пример, приближённый к реальности. Есть список заказов — нужно посчитать общую выручку по выполненным заказам дороже $50:

Три метода, один конвейер — и никакой возни со счётчиками циклов. Каждый шаг сам говорит, что он делает. Два filter подряд можно было бы объединить в один, но в таком виде код читается нормально, а при отладке иногда даже удобнее.

Дальше: Map и Set

Массивы хорошо справляются с упорядоченными последовательностями, но становятся неуклюжими, когда нужен быстрый поиск по ключу или коллекция уникальных значений. В JavaScript для этих задач есть две встроенные структуры — Map и Set, — и именно о них пойдёт речь на следующей странице.

Часто задаваемые вопросы

В чём разница между map, filter и reduce?

map преобразует каждый элемент и возвращает новый массив той же длины. filter оставляет только те элементы, которые прошли проверку, и возвращает новый массив (обычно короче исходного). reduce проходит по массиву и сворачивает его в одно значение — сумму, объект, другой массив, что угодно, что вы собираете по ходу.

Чем forEach отличается от map?

forEach просто выполняет функцию для каждого элемента и возвращает undefined — он нужен для побочных эффектов. map тоже вызывает функцию для каждого элемента, но возвращает новый массив с результатами. Нужен преобразованный массив — берите map. Нужно просто что-то сделать с каждым элементом, а результат не важен — forEach или цикл for...of.

Какие методы массива мутируют исходный массив?

Мутирующие методы: push, pop, shift, unshift, splice, sort, reverse, fill и copyWithin. Все остальные — map, filter, slice, concat, flat, flatMap, find, some, every, reduce — исходный массив не трогают и возвращают новое значение.

Когда использовать slice, а когда splice?

slice(start, end) возвращает поверхностную копию участка массива и не трогает оригинал. splice(start, deleteCount, ...items) мутирует массив — удаляет и/или вставляет элементы на месте и возвращает то, что удалил. Запомнить легко: slice — безопасный срез, splice — хирургическая правка.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ