Перейти к содержанию
Innovialabs

Функции высшего порядка: map, filter и reduce на примерах

Основы4 мин

Коротко

Функция высшего порядка принимает или возвращает другую функцию; map преобразует каждый элемент, filter оставляет прошедшие проверку, а reduce сворачивает коллекцию в одно значение.

Короткий ответ

Функция высшего порядка — это функция, которая принимает другую функцию как аргумент или возвращает её. Три самые частые работают с коллекциями:

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

Они заменяют типичные циклы коротким описанием того, что нужно получить, а не как перебирать.

Функции — это значения

В JavaScript и Python функция — обычное значение. Её можно сохранить в переменную, передать в другую функцию и вернуть.

const double = (x) => x * 2;
const apply = (fn, value) => fn(value);
apply(double, 5); // 10
def double(x):
    return x * 2

def apply(fn, value):
    return fn(value)

apply(double, 5)  # 10

map, filter и reduce — именно такие функции: цикл встроен внутрь, а вы передаёте только шаг.

От цикла к map, filter, reduce

Задача: из списка заказов взять оплаченные и посчитать общую сумму.

Циклом:

let total = 0;
for (const order of orders) {
  if (order.paid) {
    total += order.amount;
  }
}

Функциями высшего порядка:

const total = orders
  .filter((o) => o.paid)
  .map((o) => o.amount)
  .reduce((sum, a) => sum + a, 0);

То же на Python:

from functools import reduce

paid = filter(lambda o: o["paid"], orders)
amounts = map(lambda o: o["amount"], paid)
total = reduce(lambda s, a: s + a, amounts, 0)

В идиоматичном Python обычно предпочитают comprehensions и встроенные функции:

total = sum(o["amount"] for o in orders if o["paid"])

Учтите: в Python 3 map и filter возвращают ленивые итераторы, а не списки. Оберните результат в list(), если он нужен повторно.

Как работает reduce

reduce принимает функцию с двумя аргументами — аккумулятором и текущим элементом — и начальное значение.

const counts = ["a", "b", "a"].reduce((acc, letter) => {
  acc[letter] = (acc[letter] ?? 0) + 1;
  return acc;
}, {});
// { a: 2, b: 1 }

Всегда передавайте начальное значение. Без него JavaScript берёт первый элемент как стартовый и бросает TypeError на пустом массиве.

Цепочки

Цепочка читается сверху вниз, как рецепт: отфильтровать, преобразовать, агрегировать. Несколько практических правил:

  • Ставьте filter перед map, когда это возможно, — преобразуете меньше элементов.
  • Держите колбэки чистыми: без побочных эффектов и изменения внешних переменных. Тогда каждый шаг легко тестировать отдельно.
  • Давайте сложным колбэкам имена: .filter(isActiveUser) читается лучше трёхстрочной стрелочной функции.
  • Помните, что каждый шаг цепочки массива в JavaScript создаёт новый промежуточный массив. Для обычных данных интерфейса это нормально; для очень больших объёмов в горячем коде — измеряйте.

Пределы читаемости

Функции высшего порядка не делают код читаемым автоматически. Тревожные признаки:

  • Колбэк reduce длиннее нескольких строк, с ветвлениями и вложенными объектами.
  • Цепочки из пяти и более шагов, где без комментариев непонятно, какая форма у данных между шагами.
  • map только ради побочных эффектов (логирование, отправка запросов) — для этого есть forEach или цикл.

Когда обычный цикл лучше

СитуацияЛучший выбор
Нужно остановиться раньше (break)цикл или find / some / every
Последовательные вызовы await один за другимfor...of с await
Сложное состояние с несколькими накопителямицикл
Критичный к скорости код на огромных массивахцикл, после замеров
Простое преобразование, фильтр или суммаmap / filter / reduce

forEach, map и reduce не ждут асинхронные колбэки по очереди. map с async-функцией возвращает массив промисов — его можно передать в Promise.all, если нужно именно параллельное выполнение.

Частые ошибки

  • Забыть return в колбэке с фигурными скобками — получатся значения undefined.
  • Изменять исходный массив внутри map.
  • Не передать начальное значение в reduce.
  • Передать функцию с лишними параметрами напрямую: ["1", "2", "3"].map(parseInt) даёт [1, NaN, NaN], потому что map передаёт ещё и индекс, который становится основанием системы счисления.

Частые вопросы

Небольшие накладные расходы возможны, но в большинстве прикладного кода разница не важна. Оптимизируйте только там, где профилирование показало реальное узкое место.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.