Урок 15 из 49 · Месяц 2. Git и введение в JavaScript
Методы перебора массива: map, forEach, filter, find
Содержание урока
Когда данные приходят списком — товары, пользователи, сообщения, — с ними постоянно нужно что-то делать: показать каждый, отобрать подходящие, найти один нужный, посчитать сумму. В этом уроке разберём методы перебора массива: forEach, map, filter, find, findIndex, some, every, reduce и sort. Они заменяют громоздкие циклы короткими и понятными записями и встречаются в каждом React-проекте.
Как устроены методы перебора
Все эти методы принимают колбэк — функцию, которую они вызывают для каждого элемента массива по очереди (что такое колбэк, разбирали в уроке Функции, замыкания и колбэки).
В колбэк передаются три аргумента:
const fruits = ["яблоко", "банан", "киви"];
fruits.forEach((item, index, array) => {
console.log(item, index, array.length);
});
// яблоко 0 3
// банан 1 3
// киви 2 3
item— текущий элемент;index— его номер;array— весь массив (нужен редко).
Чаще всего используют только первый, иногда второй аргумент. Имена можно давать любые: product, user, i.
Для всех примеров урока возьмём один массив товаров:
const products = [
{ id: 1, title: "Футболка", price: 900, category: "одежда", inStock: true },
{ id: 2, title: "Кружка", price: 350, category: "посуда", inStock: true },
{ id: 3, title: "Худи", price: 2500, category: "одежда", inStock: false },
{ id: 4, title: "Тарелка", price: 450, category: "посуда", inStock: true },
{ id: 5, title: "Кепка", price: 700, category: "одежда", inStock: true },
];
forEach — просто перебрать
forEach вызывает колбэк для каждого элемента и ничего не возвращает (undefined). Используется, когда нужно что-то сделать с каждым элементом: вывести, добавить на страницу.
products.forEach((product) => {
console.log(`${product.title}: ${product.price} сом`);
});
Это то же самое, что цикл for...of, только в виде метода. Пример с DOM:
const list = document.querySelector("#list");
products.forEach((product) => {
const li = document.createElement("li");
li.textContent = `${product.title} — ${product.price} сом`;
list.append(li);
});
map — преобразовать каждый элемент
map создаёт новый массив той же длины, где каждый элемент — результат колбэка. Аналогия: конвейер, на котором каждую деталь покрасили — деталей столько же, но они другие.
const numbers = [1, 2, 3, 4];
const doubled = numbers.map((n) => n * 2);
console.log(doubled); // [2, 4, 6, 8]
console.log(numbers); // [1, 2, 3, 4] — исходный массив не изменился
С объектами:
const titles = products.map((product) => product.title);
console.log(titles); // ["Футболка", "Кружка", "Худи", "Тарелка", "Кепка"]
const withDiscount = products.map((product) => ({
...product,
price: product.price * 0.9,
}));
console.log(withDiscount[0]); // { id: 1, title: "Футболка", price: 810, ... }
Разбор второго примера:
- стрелка возвращает объект, поэтому он обёрнут в круглые скобки
({ ... }); ...productкопирует все свойства товара (оператор spread, подробно — в уроке Деструктуризация, spread и rest);price: ...перезаписывает цену в копии. Исходные товары не меняются.
map часто используют, чтобы собрать HTML-разметку:
const html = products
.map((p) => `<li>${p.title} — ${p.price} сом</li>`)
.join("");
document.querySelector("#list").innerHTML = html;
Так можно делать с вашими данными. Текст, введённый пользователем, через innerHTML вставлять нельзя (риск XSS) — для него textContent.
filter — отобрать подходящие
filter создаёт новый массив только из тех элементов, для которых колбэк вернул true. Аналогия: сито.
const inStock = products.filter((p) => p.inStock);
console.log(inStock.length); // 4
const cheap = products.filter((p) => p.price < 800);
console.log(cheap.map((p) => p.title)); // ["Кружка", "Тарелка", "Кепка"]
const clothes = products.filter((p) => p.category === "одежда" && p.inStock);
console.log(clothes.map((p) => p.title)); // ["Футболка", "Кепка"]
Если ничего не подошло, filter вернёт пустой массив [], а не undefined.
Типичный приём — удалить элемент по id, не изменяя исходный массив:
const withoutMug = products.filter((p) => p.id !== 2);
find и findIndex — найти один элемент
find возвращает первый элемент, для которого колбэк вернул true, или undefined, если такого нет. Как только элемент найден, перебор останавливается.
const hoodie = products.find((p) => p.id === 3);
console.log(hoodie.title); // "Худи"
const expensive = products.find((p) => p.price > 10000);
console.log(expensive); // undefined
findIndex — то же самое, но возвращает индекс элемента или -1:
const index = products.findIndex((p) => p.title === "Тарелка");
console.log(index); // 3
Есть ещё findLast и findLastIndex — ищут с конца массива.
| Нужно | Метод | Результат |
|---|---|---|
| Все подходящие | filter |
Массив (может быть пустым) |
| Первый подходящий | find |
Элемент или undefined |
| Позиция первого | findIndex |
Число или -1 |
some и every — проверить условие
some— есть ли хотя бы один подходящий элемент →true/false;every— подходят ли все элементы →true/false.
const hasOutOfStock = products.some((p) => !p.inStock);
console.log(hasOutOfStock); // true — Худи нет в наличии
const allCheap = products.every((p) => p.price < 3000);
console.log(allCheap); // true
Если нужно просто проверить, есть ли значение в массиве примитивов, проще includes: ["a", "b"].includes("a").
reduce — свести массив к одному значению
reduce проходит по массиву и «накапливает» результат. Аналогия: копилка, в которую на каждом шаге что-то добавляют.
const numbers = [10, 20, 30];
const total = numbers.reduce((acc, n) => acc + n, 0);
console.log(total); // 60
Разбор:
acc(accumulator, «накопитель») — результат, накопленный к текущему шагу;n— текущий элемент;0— начальное значение накопителя;- то, что вернул колбэк, становится новым
accна следующем шаге.
| Шаг | acc |
n |
Вернули |
|---|---|---|---|
| 1 | 0 | 10 | 10 |
| 2 | 10 | 20 | 30 |
| 3 | 30 | 30 | 60 |
Сумма стоимости корзины:
const cart = [
{ title: "Футболка", price: 900, qty: 2 },
{ title: "Кружка", price: 350, qty: 3 },
];
const cartTotal = cart.reduce((sum, item) => sum + item.price * item.qty, 0);
console.log(cartTotal); // 2850
Группировка товаров по категориям:
const byCategory = products.reduce((groups, p) => {
if (!groups[p.category]) {
groups[p.category] = [];
}
groups[p.category].push(p.title);
return groups;
}, {});
console.log(byCategory);
// { одежда: ["Футболка", "Худи", "Кепка"], посуда: ["Кружка", "Тарелка"] }
sort — сортировка
sort сортирует массив на месте (меняет исходный!) и принимает функцию сравнения:
const nums = [10, 1, 5, 100];
console.log([...nums].sort()); // [1, 10, 100, 5] — как строки!
console.log([...nums].sort((a, b) => a - b)); // [1, 5, 10, 100] — по возрастанию
console.log([...nums].sort((a, b) => b - a)); // [100, 10, 5, 1] — по убыванию
Без функции сравнения sort сравнивает элементы как строки, поэтому "100" идёт раньше "5". Функция (a, b) => a - b возвращает отрицательное число, если a должно стоять раньше b.
Чтобы не портить исходный массив, есть современный метод toSorted, который возвращает новый массив:
const byPrice = products.toSorted((a, b) => a.price - b.price);
console.log(byPrice.map((p) => p.title)); // ["Кружка", "Тарелка", "Кепка", "Футболка", "Худи"]
const byName = products.toSorted((a, b) => a.title.localeCompare(b.title));
localeCompare правильно сравнивает строки с учётом алфавита, в том числе русского.
Цепочки методов
Методы, возвращающие массив (map, filter, toSorted), можно вызывать подряд:
const result = products
.filter((p) => p.inStock)
.filter((p) => p.category === "одежда")
.toSorted((a, b) => a.price - b.price)
.map((p) => `${p.title} (${p.price} сом)`);
console.log(result); // ["Кепка (700 сом)", "Футболка (900 сом)"]
Читается как предложение: «взять товары в наличии → только одежду → отсортировать по цене → превратить в строки». Каждый шаг работает с результатом предыдущего.
Шпаргалка
| Метод | Возвращает | Меняет исходный | Когда использовать |
|---|---|---|---|
forEach |
undefined |
Нет | Сделать что-то с каждым |
map |
Новый массив той же длины | Нет | Преобразовать каждый |
filter |
Новый массив (короче или равный) | Нет | Отобрать подходящие |
find |
Элемент или undefined |
Нет | Найти один |
findIndex |
Индекс или -1 |
Нет | Найти позицию |
some / every |
true / false |
Нет | Проверить условие |
reduce |
Любое значение | Нет | Посчитать итог |
sort |
Тот же массив | Да | Сортировка на месте |
toSorted |
Новый массив | Нет | Сортировка без изменений |
Типичные ошибки
- Ждать результат от
forEach:const x = arr.forEach(...)— всегдаundefined. Нуженmap. - Забыли
returnв колбэке с{}—mapдастundefined,filterвернёт пустой массив. findвместоfilter(и наоборот):findвозвращает один объект, а не массив.sort()без функции для чисел — сортировка как строк.sortпортит исходные данные — используйтеtoSortedили копию[...arr].sort().- Обращение к свойству результата
findбез проверки: если ничего не нашлось,undefined.titleвызовет ошибку.
Практика
- Квадраты. Дан
[1, 2, 3, 4, 5]. С помощьюmapполучите квадраты, с помощьюfilter— только чётные квадраты. Ожидаемый результат:[1, 4, 9, 16, 25]и[4, 16]. - Поиск пользователя. Создайте массив пользователей
{ id, name, age }. Найдите пользователя сid === 3черезfindи проверьте черезevery, что все старше 16. Ожидаемый результат: объект пользователя иtrue/false. - Корзина. Для массива
{ title, price, qty }посчитайте общую сумму черезreduceи количество товаров (суммаqty). Ожидаемый результат: два числа. - Каталог на странице. Выведите массив
productsна страницу черезmap+join, а над списком — кнопки «Все», «Одежда», «Посуда», которые перерисовывают список черезfilter. Ожидаемый результат: список меняется по кнопкам. - Сортировка и поиск. Добавьте к каталогу поле поиска (событие
input) и выпадающий список сортировки по цене. Используйте цепочкуfilter→toSorted→map. Ожидаемый результат: фильтр, поиск и сортировка работают вместе.
Итоги
- Методы перебора принимают колбэк
(item, index, array)и вызывают его для каждого элемента. forEach— действие,map— преобразование,filter— отбор,find— поиск одного.someиeveryотвечают на вопрос «да или нет»,reduceсводит массив к одному значению.sortменяет исходный массив,toSorted— нет; для чисел нужна функция(a, b) => a - b.- Методы можно объединять в цепочки — так код читается как последовательность шагов.