Перейти к содержанию
  • Лента
  • Категории
  • Последние
  • Метки
  • Популярные
  • Пользователи
  • Группы
Свернуть
exlends
Категории
  1. Главная
  2. Категории
  3. Языки программирования
  4. JavaScript
  5. map/filter/reduce vs циклы: сокращаем код в 5 раз

map/filter/reduce vs циклы: сокращаем код в 5 раз

Запланировано Прикреплена Закрыта Перенесена JavaScript
mapfilterreduceоптимизация кодаjavascript
1 Сообщения 1 Постеры 5 Просмотры
  • Сначала старые
  • Сначала новые
  • По количеству голосов
Ответить
  • Ответить, создав новую тему
Авторизуйтесь, чтобы ответить
Эта тема была удалена. Только пользователи с правом управления темами могут её видеть.
  • hannadevH Не в сети
    hannadevH Не в сети
    hannadev
    написал отредактировано
    #1

    Помнишь то ощущение, когда смотришь на свой код с циклами for и понимаешь, что можно всё переписать в три строки? Вот оно самое. Сегодня разбираем, почему функциональные методы массивов — это не просто синтаксический сахар, а реальный способ писать чище и быстрее. На примере обработки заказов покажу, как один и тот же результат может выглядеть совершенно по-разному.

    Теория на бумаге — это скучно. Давайте сразу к практике: возьмём реальный кейс и посмотрим, как старый школьный подход рыхлеет рядом с функциональным подходом. Плюс обговорим мифы про производительность, потому что много разработчиков до сих пор боятся map и filter.

    Классический for: наследство легаси

    Ладно, начнём с грустного. Представь, что ты получил список заказов и нужно:

    1. Выбрать только активные заказы (со статусом ‘active’)
    2. Вытащить сумму каждого с налогом (+10%)
    3. Подсчитать общую сумму

    Таков вот классик жанра:

    const orders = [
      { id: 1, status: 'active', amount: 100 },
      { id: 2, status: 'cancelled', amount: 50 },
      { id: 3, status: 'active', amount: 200 },
      { id: 4, status: 'active', amount: 75 }
    ];
    
    let total = 0;
    const activeOrders = [];
    
    for (let i = 0; i < orders.length; i++) {
      if (orders[i].status === 'active') {
        activeOrders.push(orders[i]);
      }
    }
    
    for (let i = 0; i < activeOrders.length; i++) {
      total += activeOrders[i].amount * 1.1;
    }
    
    console.log(total); // 385
    

    Видишь проблему? Два цикла, переменные-приёмники, мутирующие структуры, логика размазана по коду. Код читается линейно, но концептуально — это три разных операции, которые как-то странно расслоились по функции. Новичок, открывший такой файл, потратит две минуты на понимание того, что вообще происходит.

    В реальных приложениях таких циклов обычно не два, а целая лестница вложенных условий и промежуточных переменных. Это и есть тот самый раздутый код, который потом сложно рефакторить и в котором легко завести баг.

    Функциональный подход: map, filter, reduce

    А теперь тот же результат в три линии:

    const total = orders
      .filter(order => order.status === 'active')
      .map(order => order.amount * 1.1)
      .reduce((sum, amount) => sum + amount, 0);
    
    console.log(total); // 385
    

    Так, что тут творится? Давай разберём каждый метод отдельно, чтобы понять, почему это работает.

    filter() — это просто: вызывается коллбэк для каждого элемента массива, и если функция возвращает true, элемент попадает в новый массив. После filter() остаются только активные заказы. Исходный массив не трогается — это иммютабельность, которую обожают функциональные программисты.

    map() — трансформирует каждый элемент по правилу и возвращает новый массив такой же длины. Тут мы берём каждый активный заказ, умножаем amount на 1.1 (добавляем налог), и получаем массив цифр: [110, 220, 82.5].

    reduce() — это аккумулятор. Вот тут многие запутываются. Суть проста: reduce вызывает коллбэк для каждого элемента, но передаёт ему два параметра — накопленное значение (accumulator) и текущий элемент. На каждой итерации коллбэк возвращает новое значение accumulator’а, которое становится первым параметром в следующей итерации. Второй параметр reduce() — это начальное значение accumulator’а (в нашем случае 0).

    Операция проста: (110 + 0) -> 110 -> (110 + 220) -> 330 -> (330 + 82.5) -> 412.5.

    Фактически, мы цепляем три операции в одну строку. Не надо создавать переменные-помощники, не надо писать условия в цикле. Код читается как фраза: «отфильтруй активные заказы, возьми их суммы с налогом, сложи всё».

    Зачем это нужно: преимущества очевидны

    Давай честно поговорим, почему функциональный подход побеждает:

    Читаемость — это самое главное. Цепочка методов показывает намерение кода. Когда кто-то видит .filter().map().reduce(), он сразу понимает логику трансформации данных. С классическим циклом нужно разбирать каждую строку.

    Меньше ошибок — потому что меньше места для ошибок. Не нужно создавать промежуточные переменные, индексы, условия в циклах. Каждый метод имеет узкую ответственность и делает ровно то, для чего предназначен. Плюс отсутствие мутаций — исходный массив остаётся чистым.

    Легче рефакторить — если нужно добавить ещё один фильтр или трансформацию, просто добавляешь ещё один метод в цепь. С циклом пришлось бы искать, куда вставить новую логику.

    Комбинируемость — функциональные методы идеально работают вместе. Ты можешь строить сложные трансформации, не думая о промежуточных переменных.

    Вот наглядное сравнение:

    Аспект Классический for map/filter/reduce
    Строк кода 10-15 3-5
    Переменные-помощники Несколько Нет
    Мутирование данных Да Нет
    Намерение кода Нужно разбирать Очевидно
    Легко добавить фильтр Сложно Просто
    Ошибки с индексами Возможны Исключены

    Реальный пример: обработка заказов на стероидах

    Ок, сделаем ситуацию более жесткой. Теперь задача:

    1. Фильтруем только активные заказы
    2. Исключаем заказы меньше 50 (минимальный порог)
    3. Добавляем налог (+10%)
    4. Добавляем комиссию обработки (+2% от суммы с налогом)
    5. Группируем по типу платежа
    6. Считаем общую сумму по каждой группе

    На классическом for? Адский переплёт условий, вложенных циклов и переменных. А вот функциональный подход:

    const orders = [
      { id: 1, status: 'active', amount: 100, payment: 'card' },
      { id: 2, status: 'cancelled', amount: 50, payment: 'card' },
      { id: 3, status: 'active', amount: 200, payment: 'cash' },
      { id: 4, status: 'active', amount: 75, payment: 'card' },
      { id: 5, status: 'active', amount: 40, payment: 'cash' }
    ];
    
    const result = orders
      .filter(order => order.status === 'active')
      .filter(order => order.amount >= 50)
      .map(order => ({
        ...order,
        total: order.amount * 1.1 * 1.02
      }))
      .reduce((acc, order) => {
        const key = order.payment;
        if (!acc[key]) acc[key] = 0;
        acc[key] += order.total;
        return acc;
      }, {});
    
    console.log(result);
    // { card: 374.64, cash: 222 }
    

    Видишь, как это масштабируется? Два фильтра — потому что нужны два условия. map() — трансформируем структуру, добавляем налог и комиссию. reduce() — группируем и суммируем. Каждая операция — логически отдельная, но они работают как единый конвейер.

    По сравнению с циклом на 20+ строк это просто песня.

    Миф про производительность: он не нужен

    Многие мидлы всё ещё боятся, что map и filter медленнее циклов. Это было правдой в 2010 году, но сейчас в 2026 это просто смешно. JavaScript-движки (V8, SpiderMonkey и прочие) натурально оптимизировали эти методы. Когда ты пишешь .filter().map(), под капотом это вычисляется примерно так же быстро, как if-ы в цикле.

    Разница может быть в миллисекундах на массивах из сотен тысяч элементов, и то из-за других причин. Для 99% реальных случаев это вообще не заметно. Выигрыш в читаемости и поддерживаемости кода стоит гораздо дороже, чем эта условная миллисекунда.

    Есть ещё один важный момент: если ты пишешь .filter().map().reduce(), то браузер/движок может применить оптимизации, которые он не применит к классическому циклу, потому что там логика более чёткая. Плюс, функциональный стиль часто позволяет лучше параллелизировать код.

    Когда всё же нужен for

    Не буду притворяться, что map/filter/reduce — волшебное средство от всех болезней. Есть сценарии, где классический цикл — правильный выбор:

    • Нужно рано выйти из цикла — for с break / continue выглядит проще, чем трюки с find() или some()
    • Нужна сложная трансформация с побочными эффектами — если логика требует нескольких переменных и условных операций, цикл может быть понятнее
    • Производительность критична — если профилер показал, что именно эти несколько циклов съедают время, стоит переписать на более низкоуровневый код
    • Нужна асинхронность — forEach с async/await выглядит понятнее, чем цепочка промисов

    Но в большинстве случаев — в 80-90% — функциональные методы выигрывают. Приучи себя писать так по умолчанию, и только если есть серьёзная причина — переходи на for.

    Комбинирование методов: выше, выше

    Ключевой трюк функционального подхода — ты можешь комбинировать методы с другими функциями. Например, вместо вложенного map()'а внутри reduce():

    // Вот так пишут новички
    const totals = orders
      .filter(order => order.status === 'active')
      .reduce((acc, order) => {
        acc.push(order.amount * 1.1);
        return acc;
      }, [])
      .reduce((sum, amount) => sum + amount, 0);
    
    // А вот так мудрецы
    const total = orders
      .filter(order => order.status === 'active')
      .map(order => order.amount * 1.1)
      .reduce((sum, amount) => sum + amount, 0);
    

    Второй вариант понятнее, потому что каждый метод делает одно — именно то, для чего он предназначен. Не смешивай ответственность методов — это основной принцип функционального программирования в JavaScript.

    Практический совет: пиши цепи читаемо

    Есть одна деталь, которую часто упускают: форматирование цепи методов. Вот неправильно:

    const total = orders.filter(o => o.status === 'active').map(o => o.amount * 1.1).reduce((s, a) => s + a, 0);
    

    Это в одну строку — просто ад. Вот правильно:

    const total = orders
      .filter(order => order.status === 'active')
      .map(order => order.amount * 1.1)
      .reduce((sum, amount) => sum + amount, 0);
    

    Каждый метод — на своей строке. Аргументы коллбэков назови нормально, не сокращай на o, a, s. Это 30 миллисекунд типизации, которые сэкономят часы отладки потом.

    Ещё момент: если коллбэк сложный, используй скобки и функции:

    const calculateTotal = (sum, amount) => sum + amount;
    const total = orders
      .filter(order => order.status === 'active')
      .map(order => order.amount * 1.1)
      .reduce(calculateTotal, 0);
    

    Или даже вынеси логику в отдельные функции:

    const isActive = order => order.status === 'active';
    const addTax = order => order.amount * 1.1;
    const sum = (total, amount) => total + amount;
    
    const total = orders
      .filter(isActive)
      .map(addTax)
      .reduce(sum, 0);
    

    Это выглядит как излишество, пока ты не начнёшь использовать эти функции в других местах кода. Тогда понимаешь, что это просто переиспользуемые блоки логики.

    Что там дальше

    Функциональные методы массивов — это только начало кроличьей норы функционального программирования. Есть ещё flatMap(), find(), some(), every(), которые решают специфичные задачи. Есть entire функциональные библиотеки типа Ramda, которые позволяют писать код совсем в другом стиле. Но базовый набор map/filter/reduce — это то, что каждый фронтенд-разработчик должен уметь использовать на автомате, без раздумий.

    Ключевой вывод: не пиши циклы, когда есть функциональные методы. Это не про хипстерство и не про кодовый гольф. Это про то, что функциональный стиль просто лучше масштабируется, проще читается и вводит меньше ошибок. Попробуй переписать свой последний проект с использованием этих методов — гарантирую, код станет чище без какого-либо экспоненциального усложнения.

    1 ответ Последний ответ
    0

    Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.

    Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.

    С вашими комментариями этот пост мог бы стать ещё лучше 💗

    Зарегистрироваться Войти

    Категории

    • Главная
    • Новости
    • Фронтенд
    • Бекенд
    • Языки программирования

    Контакты

    • Сотрудничество
    • info@exlends.com

    © 2024 - 2026 ExLends, Inc. Все права защищены.

    Политика конфиденциальности
    • Войти

    • Нет учётной записи? Зарегистрироваться

    • Войдите или зарегистрируйтесь для поиска.
    • Первое сообщение
      Последнее сообщение
    0
    • Лента
    • Категории
    • Последние
    • Метки
    • Популярные
    • Пользователи
    • Группы