У массивов есть свой набор инструментов
Массивы в 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,unshiftsplice,sort,reversefill,copyWithin
Немутирующие (возвращают новый массив или значение, оригинал не трогают):
map,filter,slice,concatflat,flatMapfind,findIndex,some,every,includes,indexOfreduce,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 — хирургическая правка.