Перейти к содержимому

43. map, filter, reduce детально

![Иллюстрация к уроку](/lessons/javascript-map-filter-reduce-deep.png)
Добро пожаловать! Сегодня мы разберем три мощных метода массивов в JavaScript: `map`, `filter` и `reduce`. Они позволяют элегантно и эффективно преобразовывать и обрабатывать данные.
## map: Преобразование каждого элемента
Метод `map` создает *новый* массив, применяя функцию к *каждому* элементу исходного массива. Представьте, что у вас есть конвейер, где каждый элемент массива проходит через станцию обработки (вашу функцию) и выходит уже измененным.
```javascript
const numbers = [1, 2, 3, 4, 5];
// Увеличиваем каждый элемент массива на 2
const doubledNumbers = numbers.map(function(number) {
return number * 2;
});
console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]
console.log(numbers); // Output: [1, 2, 3, 4, 5] (Исходный массив не изменился!)

Здесь numbers.map(...) применяет анонимную функцию function(number) { return number * 2; } к каждому числу в массиве numbers. Результат - новый массив doubledNumbers с удвоенными значениями. Обратите внимание, что исходный массив numbers остался без изменений.

Метод filter создает новый массив, содержащий только те элементы исходного массива, для которых функция-условие вернула true. Это как сито, которое пропускает только то, что соответствует критериям.

const numbers = [1, 2, 3, 4, 5, 6];
// Отбираем только четные числа
const evenNumbers = numbers.filter(function(number) {
return number % 2 === 0;
});
console.log(evenNumbers); // Output: [2, 4, 6]
console.log(numbers); // Output: [1, 2, 3, 4, 5, 6] (Исходный массив не изменился!)

В этом примере numbers.filter(...) применяет функцию function(number) { return number % 2 === 0; } к каждому числу. Если число четное (остаток от деления на 2 равен 0), то функция возвращает true, и число попадает в новый массив evenNumbers.

Метод reduce сводит массив к одному значению (числу, строке, объекту и т.д.), последовательно применяя функцию к каждому элементу и аккумулятору (значению, полученному на предыдущем шаге). Это как калькулятор, который последовательно применяет операцию к числам и накапливает результат.

const numbers = [1, 2, 3, 4, 5];
// Суммируем все элементы массива
const sum = numbers.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
}, 0); // 0 - это начальное значение аккумулятора
console.log(sum); // Output: 15
console.log(numbers); // Output: [1, 2, 3, 4, 5] (Исходный массив не изменился!)

Здесь numbers.reduce(...) применяет функцию function(accumulator, currentValue) { return accumulator + currentValue; }. accumulator - это текущая сумма (начинается с 0), а currentValue - текущий элемент массива. Функция возвращает сумму accumulator и currentValue, которая становится новым значением accumulator для следующей итерации.

Представьте, что у вас есть список товаров в интернет-магазине.

  • map: Вы можете использовать map для отображения цены каждого товара в определенной валюте.
  • filter: Вы можете использовать filter для отображения только товаров, находящихся в наличии.
  • reduce: Вы можете использовать reduce для вычисления общей стоимости всех товаров в корзине.

В React, Vue и Angular эти методы активно используются для обработки данных, полученных с сервера, для рендеринга списков, фильтрации результатов поиска и агрегации данных. Например, в React:

// Пример в React (псевдокод)
const products = [/* ... массив объектов продуктов ... */];
const availableProducts = products.filter(product => product.inStock);
const productNames = availableProducts.map(product => product.name);
return (
<ul>
{productNames.map(name => <li key={name}>{name}</li>)}
</ul>
);
  • map, filter и reduce не изменяют исходный массив.
  • map создает новый массив с преобразованными элементами.
  • filter создает новый массив с отфильтрованными элементами.
  • reduce сводит массив к одному значению.
  • Эти методы часто используются вместе для решения сложных задач обработки данных.
  • Важно понимать, что каждый метод возвращает.
## Интерактивный пример
<Playground client:load
template="static"
files={{
"/index.html": `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
body { background: #1e1e2e; color: #cdd6f4; font-family: sans-serif; padding: 20px; margin: 0; }
button { background: #89b4fa; color: #1e1e2e; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; margin: 4px; font-size: 14px; }
button:hover { background: #74c7ec; }
button.active { background: #a6e3a1; }
h3 { color: #89b4fa; margin: 16px 0 8px; }
.box { background: #313244; border-radius: 8px; padding: 16px; margin: 10px 0; }
.step { background: #181825; border-radius: 6px; padding: 12px; margin: 6px 0; font-family: monospace; font-size: 13px; white-space: pre; }
.step-label { color: #cba6f7; font-weight: bold; margin-bottom: 4px; }
.arr { color: #a6e3a1; } .acc { color: #fab387; } .desc { color: #a6adc8; font-size: 12px; }
input { background: #313244; color: #cdd6f4; border: 1px solid #45475a; padding: 6px 10px; border-radius: 4px; font-size: 14px; width: 260px; }
label { font-size: 13px; color: #a6adc8; }
</style>
</head>
<body>
<h3>🔄 map → filter → reduce Pipeline</h3>
<div class="box">
<label>Числа (через запятую):</label><br>
<input id="arr" value="1, 2, 3, 4, 5, 6, 7, 8, 9, 10" style="margin:6px 0"><br>
<button onclick="runPipeline()">▶ Запустить pipeline</button>
<button onclick="demoChain()">🔗 Цепочка методов</button>
<button onclick="demoReduceObj()">📊 reduce → объект</button>
</div>
<div id="steps"></div>
<script>
function getArr() { return document.getElementById('arr').value.split(',').map(s => Number(s.trim())).filter(n => !isNaN(n)); }
function step(label, desc, value) {
return \`<div class="step"><div class="step-label">\${label}</div><div class="desc">\${desc}</div><div class="arr">\${JSON.stringify(value)}</div></div>\`;
}
window.runPipeline = function() {
const arr = getArr();
const doubled = arr.map(n => n * 2);
const filtered = doubled.filter(n => n > 10);
const sum = filtered.reduce((acc, n) => acc + n, 0);
document.getElementById('steps').innerHTML =
step('1️⃣ Исходный массив', '', arr) +
step('2️⃣ .map(n => n * 2)', 'Умножаем каждый элемент на 2', doubled) +
step('3️⃣ .filter(n => n > 10)', 'Оставляем только > 10', filtered) +
\`<div class="step"><div class="step-label">4️⃣ .reduce((acc, n) => acc + n, 0)</div><div class="desc">Суммируем все элементы</div>\` +
filtered.map((n, i) => \`<div class="acc"> acc=\${filtered.slice(0,i).reduce((a,v)=>a+v,0)} + \${n} = \${filtered.slice(0,i+1).reduce((a,v)=>a+v,0)}</div>\`).join('') +
\`<div class="arr">Итог: \${sum}</div></div>\`;
}
window.demoChain = function() {
const arr = getArr();
const r = arr.filter(n => n % 2 === 0).map(n => n * n).reduce((acc, n) => acc + n, 0);
document.getElementById('steps').innerHTML =
step('Исходный', '', arr) +
step('.filter(n => n % 2 === 0)', 'Только чётные', arr.filter(n => n % 2 === 0)) +
step('.map(n => n * n)', 'Возводим в квадрат', arr.filter(n => n % 2 === 0).map(n => n*n)) +
\`<div class="step"><div class="step-label">.reduce → сумма</div><div class="arr">Итог: \${r}</div></div>\`;
}
window.demoReduceObj = function() {
const arr = getArr();
const grouped = arr.reduce((acc, n) => {
const key = n % 2 === 0 ? 'even' : 'odd';
acc[key] = (acc[key] || []);
acc[key].push(n);
return acc;
}, {});
document.getElementById('steps').innerHTML =
step('Исходный', '', arr) +
\`<div class="step"><div class="step-label">.reduce() → группировка чётных/нечётных</div><div class="arr">\${JSON.stringify(grouped, null, 2)}</div></div>\` +
step('.reduce() → частоты', '', arr.reduce((a, n) => { a[n] = (a[n]||0)+1; return a; }, {}));
}
runPipeline();
</script>
</body>
</html>`,
}}
/>