Menu
Русский

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

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

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

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

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

index.js
Output
Click Run to see the output here.

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

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

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

index.js
Output
Click Run to see the output here.

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

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

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

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

index.js
Output
Click Run to see the output here.

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

index.js
Output
Click Run to see the output here.

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

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

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

index.js
Output
Click Run to see the output here.

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

index.js
Output
Click Run to see the output here.

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

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

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

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

index.js
Output
Click Run to see the output here.

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

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

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

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

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

index.js
Output
Click Run to see the output here.

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

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

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

index.js
Output
Click Run to see the output here.

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

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

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

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

index.js
Output
Click Run to see the output here.

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

index.js
Output
Click Run to see the output here.

Мнемоника: 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:

index.js
Output
Click Run to see the output here.

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

index.js
Output
Click Run to see the output here.

flat и flatMap

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

index.js
Output
Click Run to see the output here.

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

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

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

index.js
Output
Click Run to see the output here.

Три метода, один конвейер — и никакой возни со счётчиками циклов. Каждый шаг сам говорит, что он делает. Два 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

НАЧАТЬ