Методы массивов в JavaScript: map, filter, reduce — понятное руководство с примерами

Методы массивов в JavaScript: map, filter, reduce — понятное руководство с примерами

Методы массивов в JavaScript: map, filter, reduce — понятное руководство с примерами

Запрос «map filter reduce в JavaScript» стабильно популярен: эти три метода помогают писать компактный и понятный код без лишних циклов и мутаций. Ниже — практическое руководство с примерами, рекомендациями и типичными ошибками.

Почему именно map, filter, reduce

  • Декларативный стиль: вы описываете «что сделать», а не «как пройти по индексам».
  • Иммутабельность: методы возвращают новый массив, исходный не меняется (кроме случаев, когда вы сами мутируете элементы-объекты).
  • Композиция: их легко комбинировать в цепочки.
  • map: преобразование без мутаций

    Метод map создаёт новый массив той же длины, применяя функцию к каждому элементу.

    const prices = [100, 200, 300];
    const withTax = prices.map(p => Math.round(p * 1.1));
    console.log(withTax); // [110, 220, 330]
    

    Типичная ошибка — забыть вернуть значение при использовании фигурных скобок:

    const xs = [1, 2, 3];
    // Плохо: нет return внутри {}
    const wrong = xs.map(x => { x * 2 });
    console.log(wrong); // [undefined, undefined, undefined]
    
    // Хорошо: либо без {}, либо явно возвращаем
    const ok1 = xs.map(x => x * 2);
    const ok2 = xs.map(x => { return x * 2; });
    

    Подводный камень с parseInt:

    // Плохо: ['1','2','10'].map(parseInt) => [1, NaN, 2]
    // Потому что parseInt(value, index) принимает индекс как основание системы счисления
    const ns = ['1', '2', '10'];
    const a = ns.map(parseInt);
    console.log(a); // [1, NaN, 2]
    
    // Хорошо:
    const b = ns.map(s => parseInt(s, 10));
    const c = ns.map(Number);
    console.log(b, c); // [1, 2, 10] [1, 2, 10]
    

    filter: отбор по условию

    filter возвращает новый массив только с элементами, удовлетворяющими условию.

    const users = [
      { name: 'Ann', age: 17 },
      { name: 'Bob', age: 22 },
      { name: 'Kate', age: 19 }
    ];
    const adults = users.filter(u => u.age >= 18);
    console.log(adults.map(u => u.name)); // ['Bob', 'Kate']
    

    Удобно фильтровать «правдивые» значения:

    const raw = [0, 1, '', 'hi', null, undefined, 'JS'];
    const truthy = raw.filter(Boolean);
    console.log(truthy); // [1, 'hi', 'JS']
    

    Или исключать элементы по списку:

    const blacklist = ['tmp', 'draft'];
    const files = ['readme.md', 'tmp', 'index.js', 'draft'];
    const clean = files.filter(f => !blacklist.includes(f));
    console.log(clean); // ['readme.md', 'index.js']
    

    reduce: агрегация и не только

    reduce сводит массив к одному значению (числу, объекту, строке, массиву). Обязательно задавайте начальное значение — это избавит от граничных кейсов.

    // Сумма
    const sum = [1, 2, 3, 4].reduce((acc, x) => acc + x, 0);
    console.log(sum); // 10
    
    // Среднее
    const avg = [10, 20, 30].reduce((acc, x, i, arr) => {
      acc += x;
      return i === arr.length - 1 ? acc / arr.length : acc;
    }, 0);
    console.log(avg); // 20
    
    // Подсчёт вхождений
    const words = ['a', 'b', 'a', 'c', 'b', 'a'];
    const counters = words.reduce((acc, w) => {
      acc[w] = (acc[w] || 0) + 1;
      return acc;
    }, {});
    console.log(counters); // { a: 3, b: 2, c: 1 }
    
    // Группировка по полю
    const people = [
      { name: 'Ann', team: 'Blue' },
      { name: 'Bob', team: 'Red' },
      { name: 'Kate', team: 'Blue' }
    ];
    const byTeam = people.reduce((acc, p) => {
      (acc[p.team] ||= []).push(p);
      return acc;
    }, {});
    console.log(byTeam.Blue.map(p => p.name)); // ['Ann', 'Kate']
    
    // «Разворачивание» на один уровень (flat)
    const nested = [[1, 2], [3], [4, 5]];
    const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
    console.log(flat); // [1,2,3,4,5]
    

    Ошибка: вызывать reduce без начального значения, особенно на пустом массиве — получите исключение или некорректный результат.

    Комбинирование методов (chaining)

    Часто map, filter и reduce удобно объединять. Это улучшает читаемость и позволяет выражать намерения «по шагам».

    const products = [
      { name: 'Keyboard', price: 30, inStock: true },
      { name: 'Mouse', price: 15, inStock: false },
      { name: 'Monitor', price: 120, inStock: true }
    ];
    const cheapInStockNames = products
      .filter(p => p.inStock && p.price <= 50)
      .map(p => p.name)
      .sort();
    console.log(cheapInStockNames); // ['Keyboard']
    

    Производительность: каждая операция создаёт новый массив. В «горячих» местах можно заменить цепочку на один reduce или цикл for, но в большинстве задач выигрывает читаемость.

    Практические рецепты

    Уникальные значения и частоты:

    const tags = ['js', 'css', 'js', 'html', 'css'];
    const unique = [...new Set(tags)];
    const freq = tags.reduce((acc, t) => (acc[t] = (acc[t] || 0) + 1, acc), {});
    console.log(unique, freq); // ['js','css','html'] и { js: 2, css: 2, html: 1 }
    

    Преобразование NodeList в массив и извлечение данных:

    // const nodes = document.querySelectorAll('a.item');
    // В средах без DOM замените nodes фиктивными данными для теста
    const nodes = [{ textContent: 'A' }, { textContent: 'B' }];
    const texts = Array.from(nodes).map(n => n.textContent.trim());
    console.log(texts); // ['A', 'B']
    

    Асинхронная обработка: map вернёт массив промисов. Ждите их через Promise.all.

    const ids = [1, 2, 3];
    const promises = ids.map(async id => {
      const res = await fetch(`https://api.example.com/items/${id}`);
      return res.json();
    });
    const items = await Promise.all(promises);
    console.log(items);
    

    Типичные ошибки

  • map без return внутри фигурных скобок — получите undefined.
  • parseInt в map без второго аргумента — ловушка с основанием системы счисления.
  • reduce без начального значения — проблемы с пустыми массивами и типами.
  • Мутация объектов внутри map: вы меняете исходные данные. Используйте копии {…obj} при необходимости.
  • await внутри map без Promise.all — выполнение не будет дожидаться завершения всех операций.
  • Короткая шпаргалка

    // map: преобразование
    arr.map(x => fn(x))
    
    // filter: отбор
    arr.filter(x => condition(x))
    
    // reduce: агрегация
    arr.reduce((acc, x) => nextAcc, initial)
    

    Что дальше изучать

    Освоив «map, filter, reduce в JavaScript», имеет смысл углубиться в работу с массивами, колбэками, промисами и цепочками асинхронных операций. Если вы хотите системно прокачать навыки на практике, посмотрите программу «Прокачать JavaScript от нуля до уровня Гуру на реальных задачах». Там разобраны основы и продвинутые темы с разбором типичных ошибок.

    Итог: используйте map для преобразований, filter для отбора, reduce для агрегаций и сложных трансформаций. Старайтесь писать декларативно, держать данные неизменными и не забывать про начальные значения в reduce.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий