Функциональное программирование в JavaScript: от А до Я для Node.js

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Функциональное программирование в JavaScript: от А до Я

Функциональное программирование (ФП) — это парадигма, которая рассматривает вычисления как вычисление математических функций и избегает изменения состояния и изменяемых данных. В контексте JavaScript (Node.js) ФП становится мощным инструментом для создания предсказуемого, тестируемого и масштабируемого кода. В этой статье мы разберем ключевые концепции ФП от основ до продвинутых техник, подкрепляя теорию практическими примерами.

Основы: Чистые функции и иммутабельность

В основе ФП лежат два принципа: чистые функции и иммутабельность (неизменяемость данных).

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

// Чистая функция
const add = (a, b) => a + b;

// Нечистая функция (побочный эффект — изменение внешней переменной)
let counter = 0;
const increment = () => counter++;

Иммутабельность означает, что данные не изменяются напрямую, а создаются их новые копии с нужными изменениями. В JavaScript для этого используются методы, возвращающие новые объекты/массивы.

// Иммутабельное обновление объекта
const user = { name: 'Alice', age: 30 };
const updatedUser = { ...user, age: 31 }; // Создаем новый объект

// Иммутабельное обновление массива
const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4]; // [1, 2, 3, 4]

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

Замыкание — это функция, которая "запоминает" свое лексическое окружение даже после того, как внешняя функция завершила выполнение. Это основа для многих паттернов ФП.

const createMultiplier = (factor) => {
  // Внутренняя функция — замыкание
  return (number) => number * factor;
};

const double = createMultiplier(2);
console.log(double(5)); // 10

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

const numbers = [1, 2, 3, 4, 5];

// map — преобразует каждый элемент
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]

// filter — отбирает элементы по условию
const even = numbers.filter(n => n % 2 === 0); // [2, 4]

// reduce — сворачивает массив в одно значение
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15

Композиция функций и каррирование

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

const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);

const trim = (str) => str.trim();
const toLowerCase = (str) => str.toLowerCase();
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);

const processName = compose(capitalize, toLowerCase, trim);
console.log(processName('  JOHN DOE  ')); // 'John doe'

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

// Обычная функция
const multiply = (a, b, c) => a * b * c;

// Каррированная версия
const curriedMultiply = (a) => (b) => (c) => a * b * c;

console.log(curriedMultiply(2)(3)(4)); // 24

// Частичное применение
const multiplyBy2 = curriedMultiply(2);
const multiplyBy2And3 = multiplyBy2(3);
console.log(multiplyBy2And3(5)); // 30

Практический пример: обработка данных в Node.js

Рассмотрим реальный сценарий: у нас есть массив заказов из интернет-магазина, и нужно получить сумму всех оплаченных заказов, сделанных в 2024 году, с учетом скидки.

const orders = [
  { id: 1, amount: 100, status: 'paid', date: '2024-05-10', discount: 0.1 },
  { id: 2, amount: 200, status: 'pending', date: '2024-06-15', discount: 0 },
  { id: 3, amount: 150, status: 'paid', date: '2023-12-20', discount: 0.2 },
  { id: 4, amount: 300, status: 'paid', date: '2024-01-05', discount: 0.15 },
];

// Функциональный подход
const isPaid = (order) => order.status === 'paid';
const isIn2024 = (order) => new Date(order.date).getFullYear() === 2024;
const applyDiscount = (order) => order.amount * (1 - order.discount);
const sum = (acc, value) => acc + value;

const totalRevenue = orders
  .filter(isPaid)          // Оставляем только оплаченные
  .filter(isIn2024)        // Оставляем только за 2024 год
  .map(applyDiscount)      // Применяем скидку
  .reduce(sum, 0);         // Суммируем

console.log(totalRevenue); // 355 (100*0.9 + 300*0.85)

Каждая функция здесь — чистая и делает только одно действие. Это делает код легко читаемым, тестируемым и расширяемым. Если нужно добавить новое условие (например, фильтр по минимальной сумме), достаточно добавить еще один .filter().

Заключение

Функциональное программирование в JavaScript (Node.js) — это не просто модный тренд, а мощный инструмент для написания надежного и предсказуемого кода. Освоив чистые функции, иммутабельность, замыкания, композицию и каррирование, вы сможете:

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

Блоги

Рекомендаций по книгам