Методы массивов JavaScript: преобразование, поиск и свёртка
Автор: Казачкин Даниил Михайлович · Обновлено
Методы массивов позволяют выразить обработку коллекции через цель: отобрать элементы, преобразовать каждый, найти подходящий или вычислить общий результат. Правильный выбор метода делает видимыми форму результата и допустимые изменения исходных данных.
Среда и исходный набор
Основной пример рассчитан на Node.js 22 или новее, где есть toSorted. Сохраните его в arrays.mjs и запустите node arrays.mjs. В браузере проверьте наличие этого метода перед запуском на старом устройстве. Предполагается, что вы умеете читать массив объектов и простые стрелочные функции. Сетевых запросов и библиотек здесь нет: поведение определяется только приведёнными данными.
Пусть каталог хранит продолжительность урока в минутах и состояние публикации. Нам нужен список названий опубликованных уроков по возрастанию продолжительности и суммарное время. Это две разные проекции одного набора. Сначала зададим контракт: черновики исключаются, исходный порядок каталога сохраняется, одинаковая продолжительность дополнительно упорядочивается по идентификатору. Последнее правило делает результат предсказуемым для читателя и проверок.
Полный пример: подготовить учебный план
const lessons = [
{ id: 3, title: 'Объекты', minutes: 25, published: true },
{ id: 1, title: 'Циклы', minutes: 15, published: true },
{ id: 2, title: 'Черновик', minutes: 10, published: false },
];
const available = lessons.filter((lesson) => lesson.published);
const ordered = available.toSorted(
(a, b) => a.minutes - b.minutes || a.id - b.id,
);
const titles = ordered.map((lesson) => lesson.title);
const total = ordered.reduce((sum, lesson) => sum + lesson.minutes, 0);
console.log(JSON.stringify({ titles, total }));
console.log(lessons.map((lesson) => lesson.id).join(','));Первая строка вывода: {"titles":["Циклы","Объекты"],"total":40}. Вторая: 3,1,2, то есть исходный массив не переставлен. Фильтр оставляет целые объекты, сортировка определяет порядок, map выбирает названия, а reduce сводит коллекцию к одному числу. Промежуточные имена полезны при отладке: можно поставить точку останова после любой стадии и проверить её отдельный контракт.
filter возвращает новый массив, но сохранённые объекты не клонируются. map также создаёт массив, однако глубина независимости результата зависит от возвращаемых значений. В нашем случае названия — строки. Если вернуть исходный объект и затем изменить его поле, изменение будет видно и через каталог. Новый контейнер и новые элементы — разные свойства, которые нельзя выводить только из названия метода.
Форма результата подсказывает инструмент
Используйте map, когда каждому присутствующему элементу соответствует одно новое значение. Используйте filter, когда решение для элемента сводится к «оставить или убрать». Если нужен первый подходящий объект, find возвращает его либо undefined; если нужен только ответ о наличии, some возвращает логическое значение. every отвечает, удовлетворяют ли условию все проверяемые элементы. Для пустого массива every возвращает true: нарушителей нет, но это не доказывает наличие хотя бы одного элемента.
reduce полезен, когда результат накапливается последовательно: сумма, минимум с дополнительной информацией, сводная структура. Начальное значение определяет поведение пустого входа и тип накопителя. Для суммы это ноль; без него пустой массив вызывает ошибку. Внутри callback необходимо вернуть новый накопитель. Забытый return передаёт undefined на следующий шаг и часто превращает дальнейшую арифметику в NaN.
Обычный цикл остаётся хорошим решением для сложного управления потоком. Не нужно превращать ранний выход, несколько зависимых состояний и обработку ошибок в одну длинную свёртку. Если результат map вообще не используется, скорее всего вы выполняете побочные действия; для них понятнее for...of или forEach. Краткость выражения не равна простоте сопровождения.
Карта остальных частых операций
Сначала выберите форму результата, затем проверьте, меняет ли операция исходный массив. В таблице «не меняет» относится к самому методу: побочный эффект внутри вашего callback всё равно может изменить объекты.
| Методы | Результат и назначение | Меняют исходный массив |
|---|---|---|
slice | Новый массив выбранного диапазона | Нет |
splice | Массив удалённых элементов; вставляет и удаляет на месте | Да |
toSpliced | Новый массив с применённой вставкой или удалением | Нет |
findIndex | Индекс первого совпадения либо -1 | Нет |
includes | Логический ответ о наличии значения | Нет |
at | Элемент по индексу; отрицательный индекс отсчитывается с конца | Нет |
flat, flatMap | Новый массив после раскрытия вложенности; flatMap совмещает преобразование с раскрытием на один уровень | Нет |
reverse, toReversed | Обратный порядок; первый возвращает исходный массив, второй — новый | Только reverse |
push, pop | Новая длина после добавления / удалённый последний элемент | Да |
Поверхностные копии в этой таблице сохраняют ссылки на вложенные объекты. Не проверяйте findIndex только на истинность: нулевой индекс ложен, а отсутствие -1 истинно. Сравнивайте результат с -1 явно. Методы toSpliced и toReversed требуют достаточно современной среды, как и использованный выше toSorted.
Изменение порядка и неожиданные callback
sort меняет исходный массив. toSorted создаёт новый; если нужна совместимость со средой без него, можно скопировать контейнер и вызвать sort на копии. Для чисел обязательно задайте корректный компаратор: сортировка без него использует строковое представление и может поставить 100 перед 20. Компаратор должен давать согласованные ответы, а не случайные значения или логическое «больше».
Методы передают callback не только элемент, но и индекс. Поэтому нельзя автоматически передавать любую существующую функцию по имени. Например, у parseInt второй параметр означает основание системы счисления, а у map второй аргумент callback означает индекс. Для десятичных строк явно пишите (text) => Number.parseInt(text, 10) и отдельно определяйте, допустимы ли хвостовые символы. Преобразование и валидация — разные операции.
Ещё одна ловушка — filter(async (...) => ...): асинхронная функция возвращает Promise, который является истинным значением, а не готовым результатом проверки. Фильтр не ждёт завершения. Сначала получите результаты асинхронных проверок подходящим способом, затем фильтруйте синхронно. Подробная организация такого ожидания рассматривается в уроках о Promise и async/await.
Практика: посчитать обязательные минуты
Подготовьте функцию, которая суммирует только обязательные задания и одновременно сообщает их количество. Выход для пустого массива должен содержать два нуля. Не изменяйте входные объекты. Сначала сформулируйте, какое значение накопителя требуется после обработки первых двух записей, затем сравните с решением.
function summarize(tasks) {
return tasks.reduce((result, task) => {
if (!task.required) return result;
result.count += 1;
result.minutes += task.minutes;
return result;
}, { count: 0, minutes: 0 });
}
console.log(JSON.stringify(summarize([
{ required: true, minutes: 12 },
{ required: false, minutes: 99 },
{ required: true, minutes: 8 },
])));
console.log(JSON.stringify(summarize([])));Ожидаются {"count":2,"minutes":20} и {"count":0,"minutes":0}. Здесь меняется только свежий накопитель, созданный внутри каждого вызова. Это не мутация исходных заданий. Постоянное копирование растущего массива или объекта на каждом шаге может создавать лишнюю работу; выбирайте способ накопления с учётом размера данных и границ владения результатом.
Частые вопросы
Почему после map появились undefined?
У стрелки с фигурными скобками нет неявного возврата. Добавьте return либо используйте выражение без блока. Для возвращаемого объектного литерала нужны круглые скобки вокруг объекта.
Почему callback пропустил позицию?
Разреженный массив может иметь пустые слоты: они отличаются от элементов со значением undefined. У методов есть различия в обработке таких слотов. Для учебных и прикладных списков старайтесь создавать плотные массивы и не удалять элементы оператором delete.
Когда объединять проходы ради скорости?
Сначала измерьте обработку реального объёма. Два понятных прохода по десяткам элементов обычно легче сопровождать, чем сложная свёртка. Для сотен тысяч записей учитывайте промежуточные массивы, стоимость callback и возможность обработки порциями; оптимизируйте подтверждённую нагрузку.