Урок 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. Квадраты. Дан [1, 2, 3, 4, 5]. С помощью map получите квадраты, с помощью filter — только чётные квадраты. Ожидаемый результат: [1, 4, 9, 16, 25] и [4, 16].
  2. Поиск пользователя. Создайте массив пользователей { id, name, age }. Найдите пользователя с id === 3 через find и проверьте через every, что все старше 16. Ожидаемый результат: объект пользователя и true/false.
  3. Корзина. Для массива { title, price, qty } посчитайте общую сумму через reduce и количество товаров (сумма qty). Ожидаемый результат: два числа.
  4. Каталог на странице. Выведите массив products на страницу через map + join, а над списком — кнопки «Все», «Одежда», «Посуда», которые перерисовывают список через filter. Ожидаемый результат: список меняется по кнопкам.
  5. Сортировка и поиск. Добавьте к каталогу поле поиска (событие input) и выпадающий список сортировки по цене. Используйте цепочку filter → toSorted → map. Ожидаемый результат: фильтр, поиск и сортировка работают вместе.

Итоги

  • Методы перебора принимают колбэк (item, index, array) и вызывают его для каждого элемента.
  • forEach — действие, map — преобразование, filter — отбор, find — поиск одного.
  • some и every отвечают на вопрос «да или нет», reduce сводит массив к одному значению.
  • sort меняет исходный массив, toSorted — нет; для чисел нужна функция (a, b) => a - b.
  • Методы можно объединять в цепочки — так код читается как последовательность шагов.
Отзыв