Функциональное программирование в JavaScript: основы и практика

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

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

Начать курс

Введение в функциональное программирование в JavaScript

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

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

Основные принципы функционального программирования

Чистые функции (Pure Functions)

Чистая функция — это функция, которая:

  • Всегда возвращает один и тот же результат для одних и тех же аргументов (детерминированность).
  • Не имеет побочных эффектов (не изменяет внешние переменные, не выводит в консоль, не пишет в файлы).
// Чистая функция
const add = (a, b) => a + b;

// Нечистая функция (побочный эффект — вывод в консоль)
const impureAdd = (a, b) => {
    console.log(`Складываем ${a} и ${b}`);
    return a + b;
};

// Нечистая функция (зависит от внешнего состояния)
let counter = 0;
const increment = () => ++counter;

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

Иммутабельность (Immutability)

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

// Мутабельный подход (плохо)
const user = { name: 'Иван', age: 30 };
user.age = 31; // Меняем напрямую

// Иммутабельный подход (хорошо)
const user2 = { name: 'Мария', age: 25 };
const updatedUser = { ...user2, age: 26 };
// Исходный объект user2 остался неизменным

// Работа с массивами
const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4]; // [1, 2, 3, 4]
// numbers остался [1, 2, 3]

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

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

Функции высшего порядка (Higher-Order Functions)

Это функции, которые принимают другие функции как аргументы или возвращают их. Встроенные методы массивов 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 evens = numbers.filter(n => n % 2 === 0);
// [2, 4]

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

Замыкания (Closures)

Замыкание — это функция, которая «запоминает» переменные из области видимости, в которой она была создана, даже после того, как эта область завершила выполнение.

const createCounter = () => {
    let count = 0; // Приватная переменная
    return () => ++count;
};

const counter1 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2

const counter2 = createCounter();
console.log(counter2()); // 1 (независимый счетчик)

Замыкания широко используются для создания фабрик функций и инкапсуляции состояния в функциональном стиле.

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

Каррирование (Currying)

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

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

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

const double = curriedMultiply(2);
const triple = curriedMultiply(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

// Реализация универсального каррирования
const curry = (fn) => {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        }
        return (...more) => curried.apply(this, args.concat(more));
    };
};

const sum3 = (a, b, c) => a + b + c;
const curriedSum = curry(sum3);
console.log(curriedSum(1)(2)(3)); // 6

Каррирование позволяет создавать специализированные функции из общих, что улучшает переиспользование кода.

Композиция функций (Function Composition)

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

// Ручная композиция
const compose = (f, g) => (x) => f(g(x));

const toUpper = (str) => str.toUpperCase();
const exclaim = (str) => str + '!';
const shout = compose(exclaim, toUpper);

console.log(shout('hello')); // 'HELLO!'

// Композиция нескольких функций
const composeMany = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);

const trim = (str) => str.trim();
const capitalize = (str) => str[0].toUpperCase() + str.slice(1);
const formatName = composeMany(capitalize, trim);

console.log(formatName('  иван  ')); // 'Иван'

// Использование pipe (обратный порядок)
const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);

const processText = pipe(trim, toUpper, exclaim);
console.log(processText('  hello  ')); // 'HELLO!'

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

Блоги

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