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

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

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

Начать курс

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

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

1. Чистые функции и отсутствие побочных эффектов

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

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

Пример чистой функции:

// Чистая функция: складывает два числа
const add = (a, b) => a + b;

console.log(add(2, 3)); // 5
console.log(add(2, 3)); // 5 — всегда одинаково

Пример нечистой функции:

let counter = 0;
const increment = () => {
    counter++; // Побочный эффект: изменяет внешнюю переменную
    return counter;
};

console.log(increment()); // 1
console.log(increment()); // 2 — результат разный

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

2. Иммутабельность данных

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

Пример иммутабельного обновления объекта:

const user = { name: 'Alice', age: 30 };

// Плохо: мутация
user.age = 31;

// Хорошо: создаём новый объект
const updatedUser = { ...user, age: 31 };

console.log(user);        // { name: 'Alice', age: 30 }
console.log(updatedUser); // { name: 'Alice', age: 31 }

Для массивов также используем иммутабельные методы:

const numbers = [1, 2, 3];

// Мутация (плохо)
numbers.push(4);

// Иммутабельно (хорошо)
const newNumbers = [...numbers, 4];

console.log(numbers);    // [1, 2, 3]
console.log(newNumbers); // [1, 2, 3, 4]

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

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

3.1. map() — преобразование массива

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

const prices = [100, 200, 300];
const withTax = prices.map(price => price * 1.2);

console.log(withTax); // [120, 240, 360]
// Исходный массив не изменился
console.log(prices);  // [100, 200, 300]

3.2. filter() — фильтрация массива

Метод filter возвращает новый массив, содержащий только те элементы, для которых переданная функция вернула true.

const ages = [15, 22, 18, 30, 12];
const adults = ages.filter(age => age >= 18);

console.log(adults); // [22, 18, 30]

3.3. reduce() — агрегация данных

Метод reduce сводит массив к одному значению (числу, объекту, строке).

const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, current) => accumulator + current, 0);

console.log(sum); // 15

Комбинируя эти методы, можно решать сложные задачи в функциональном стиле:

const users = [
    { name: 'Alice', age: 25, active: true },
    { name: 'Bob', age: 17, active: false },
    { name: 'Charlie', age: 30, active: true },
];

// Получить имена активных пользователей старше 18 лет
const result = users
    .filter(user => user.active && user.age >= 18)
    .map(user => user.name);

console.log(result); // ['Alice', 'Charlie']

4. Замыкания и каррирование

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

Пример замыкания:

const createCounter = () => {
    let count = 0;
    return () => {
        count++;
        return count;
    };
};

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

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

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

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

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

const triple = curriedMultiply(3);
console.log(triple(5)); // 15

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

5. Композиция функций

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

Простая реализация композиции:

const compose = (f, g) => (x) => f(g(x));

const addOne = (x) => x + 1;
const double = (x) => x * 2;

const addOneThenDouble = compose(double, addOne);
console.log(addOneThenDouble(5)); // (5 + 1) * 2 = 12

Блоги

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