Методы массивов: map, filter, reduce
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Массив редко нужен «как есть»: чаще его надо перебрать, отсеять лишнее или свернуть в одно число. Для этих трёх задач в JavaScript есть map, filter и reduce — и, разобравшись с ними, вы почти перестанете писать циклы руками.
map — это конвейер. Вы описываете функцию «что сделать с одним элементом», а map прогоняет её по всем элементам подряд и складывает результаты в новый массив. Длина результата всегда равна длине исходного массива: один вошёл — один вышел.
Чаще всего map достают, когда из массива объектов нужен массив чего-то одного: имён, id, цен. Главное правило — функция обязана вернуть значение. Если она ничего не возвращает, на этом месте в новом массиве окажется undefined.
Стрелка без фигурных скобок возвращает значение сама. А как только вы пишете фигурные скобки — return нужен явно, иначе получите массив из undefined.
Функция внутри map, filter и forEach получает не только элемент, но и его индекс. Второй параметр можно просто не писать, если он не нужен.
filter не меняет значения — он решает, кого пропустить дальше. Ваша функция играет роль охранника на входе: вернула true — элемент попадает в новый массив, вернула false — остаётся за бортом.
Длина результата — от нуля до длины исходного массива, порядок элементов сохраняется. И обратите внимание на разницу: у map внутри функции обычно выражение-преобразование, у filter — условие. Если вы написали в filter что-то вроде n * 2, он не преобразует числа, а просто отсеет нули: любое ненулевое число для него истинно.
reduce сворачивает весь массив в одно значение: сумму, максимум, строку, объект — что угодно. У его функции два главных параметра: аккумулятор (то, что накопилось к этому шагу) и текущий элемент. Что функция вернёт — то и станет аккумулятором на следующем шаге.
Второй аргумент reduce — стартовое значение аккумулятора: 0 для суммы, {} для группировки, [] для сборки массива. Пишите его всегда, даже когда кажется, что можно без него.
Свернуть можно не только в число. Классический приём — группировка: аккумулятор-объект, ключи которого мы дописываем на ходу. Здесь важно не забыть return acc в конце — иначе на следующем шаге аккумулятором станет undefined.
Без второго аргумента reduce берёт первым аккумулятором первый элемент массива и начинает со второго. Обычно это работает — ровно до того дня, когда массив окажется пустым: тогда вызов упадёт с TypeError. Со стартовым значением пустой массив честно вернёт его.
map и filter возвращают массив, а у массива снова есть map и filter. Поэтому вызовы выстраиваются в цепочку и читаются сверху вниз, как список шагов: «взять чётные, возвести в квадрат, сложить». reduce обычно ставят последним — он возвращает уже не массив.
Порядок в цепочке — не только про смысл, но и про работу впустую: сначала filter, потом map, чтобы преобразовывать меньше элементов. И если цепочка выросла до пяти звеньев, спокойно разбейте её на две переменные с говорящими именами — это не «менее красиво», это понятнее.
map, filter и reduce не трогают исходный массив — они возвращают новое значение. Отсюда самая частая ошибка новичка: вызвать метод и не присвоить результат. Массив останется прежним, а работа пропадёт.
А вот sort, reverse, push, splice — из другого семейства: они перестраивают сам массив. Поэтому sort в середине цепочки способен незаметно испортить данные, на которые ссылается кто-то ещё. Современная замена — toSorted и toReversed: те же операции, но с копией. В React это различие станет правилом: массив, изменённый на месте, не перерисует экран. Почему так происходит, объясняет статья «Объекты и массивы в состоянии» в части IV.
forEach — тоже перебор, но он ничего не собирает и всегда возвращает undefined. Его дело — побочное действие: вывести в консоль, отправить запрос, показать alert. Если вы пытаетесь получить из forEach результат, вам был нужен map.
Выбор метода сводится к одному вопросу: что вы хотите получить на выходе?
| Метод | Что возвращает | Когда брать |
|---|---|---|
| map | новый массив той же длины | нужно преобразовать каждый элемент |
| filter | новый массив, короче или той же длины | нужно отобрать элементы по условию |
| reduce | одно значение любого типа | нужно свернуть массив в сумму, объект, строку |
| forEach | undefined | нужно просто сделать что-то с каждым |
- map возвращает новый массив той же длины: одна функция-преобразование на каждый элемент.
- filter возвращает новый массив только из тех элементов, на которых условие дало true.
- reduce сворачивает массив в одно значение — сумму, объект, строку; стартовое значение аккумулятора пишите явно.
- Эти методы не меняют исходный массив: если результат не присвоить, он потеряется. А sort, reverse и push, наоборот, меняют.
- forEach возвращает undefined и нужен только ради побочного действия; за результатом идите к map, filter или reduce.
1/3Что выведет console.log([1, 2, 3].forEach((n) => n * 2))?
2/3const a = [5, 8, 1]; a.filter((n) => n > 4); console.log(a.length); — что в консоли?
3/3Чему равно [1, 2, 3, 4].reduce((acc, n) => acc + n, 10)?
Стоимость корзины
В массиве items лежат товары: у каждого есть цена и количество. Посчитайте общую стоимость корзины одним вызовом reduce и выведите её.
Аккумулятор здесь — накопленная сумма, стартует с 0. На каждом шаге прибавляем стоимость одной позиции: цену, умноженную на количество.
Через map получилось бы в два шага: сначала items.map((item) => item.price * item.count) даст массив стоимостей, потом reduce сложит их. Оба варианта верные — второй чуть длиннее, зато нагляднее.
Длинные слова заглавными
Из массива слов оставьте только те, где больше четырёх букв, и приведите их к верхнему регистру. Исходный массив трогать нельзя — соберите новый цепочкой методов.
Сначала отбор, потом преобразование: filter оставляет подходящие слова, map меняет каждое из них.
Слово «стул» длиной ровно 4 не проходит: условие строгое, > 4, а не >= 4.