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

Digital-студия WNDER

Что такое функциональное программирование и зачем оно нужно

ФП — это стиль написания кода, где основные строительные блоки — чистые функции, а данные не изменяются. Вместо того чтобы менять переменные, вы создаёте новые значения. Звучит странно? На самом деле это как конструктор LEGO: вы не переплавляете детали, а соединяете их в новые фигуры.

Главные преимущества ФП в JavaScript:

  • Меньше багов — функции не зависят от внешнего состояния.
  • Легче тестировать — чистую функцию достаточно вызвать с разными аргументами.
  • Проще читать — код декларативен, вы говорите «что сделать», а не «как».
  • Удобно для асинхронности и работы с массивами.

Но не думайте, что ФП — это серебряная пуля. Это инструмент, который стоит применять там, где он уместен.

Правило: Если функция изменяет внешнюю переменную или зависит от неё — это не чистая функция. Старайтесь делать такими как можно больше функций.

Чистые функции: фундамент функционального стиля

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

let counter = 0;
function increment() {
  counter++;
  return counter;
}

Здесь функция меняет внешнюю переменную counter. Это побочный эффект. Чистый вариант:

function increment(value) {
  return value + 1;
}

Теперь функция не зависит от внешнего мира и не меняет его. Её легко тестировать и переиспользовать.

Вот таблица сравнения чистых и грязных функций:

Критерий Чистая функция Грязная функция
Зависимость от внешних переменных Нет Есть
Побочные эффекты Нет Есть
Предсказуемость Высокая Низкая
Тестирование Простое Сложное

Иммутабельность: не меняй, а создавай

Иммутабельность — это принцип, при котором данные после создания не изменяются. В JavaScript массивы и объекты по умолчанию мутабельны (изменяемы). Но мы можем работать с ними иммутабельно, создавая копии с нужными изменениями.

Вместо push используйте concat или spread-оператор:

const arr = [1, 2, 3];
// Мутация
arr.push(4); // [1, 2, 3, 4]

// Иммутабельно
const newArr = [...arr, 4]; // [1, 2, 3, 4]

Для объектов вместо изменения свойства создавайте новый объект:

const user = { name: 'Alice', age: 25 };
// Мутация
user.age = 26;

// Иммутабельно
const updatedUser = { ...user, age: 26 };

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

Лайфхак: Используйте Object.freeze() для предотвращения мутаций в режиме разработки. Это поможет отловить ошибки на раннем этапе.

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

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

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

const numbers = [1, 2, 3, 4, 5];
const result = [];
for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    result.push(numbers[i] * numbers[i]);
  }
}

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

const result = numbers
  .filter(n => n % 2 === 0)
  .map(n => n * n);

Согласитесь, второй вариант читается легче. Мы не описываем циклы, а говорим, что хотим сделать.

Каррирование — это преобразование функции с несколькими аргументами в последовательность функций с одним аргументом. Например:

// Обычная функция
function add(a, b) {
  return a + b;
}

// Каррированная версия
function curriedAdd(a) {
  return function(b) {
    return a + b;
  };
}

const addFive = curriedAdd(5);
addFive(3); // 8

Каррирование полезно для создания специализированных функций на основе общих.

Вот таблица популярных функций высшего порядка в JavaScript:

Метод Назначение Пример
map Преобразует каждый элемент массива [1,2,3].map(x => x * 2)
filter Оставляет элементы, удовлетворяющие условию [1,2,3].filter(x => x > 1)
reduce Сворачивает массив в одно значение [1,2,3].reduce((a,b) => a+b, 0)
forEach Выполняет действие для каждого элемента [1,2,3].forEach(console.log)

Композиция функций: собираем пазл

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

В JavaScript есть несколько способов композиции. Простейший — вложенные вызовы:

const trim = str => str.trim();
const toLowerCase = str => str.toLowerCase();
const split = str => str.split(' ');

const process = str => split(toLowerCase(trim(str)));

Но читать такое сложно. Лучше использовать функцию compose или pipe. Они делают то же самое, но нагляднее:

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

const process = pipe(trim, toLowerCase, split);
process('  Hello World  '); // ['hello', 'world']

pipe выполняет функции слева направо, compose — справа налево. Выбирайте тот, который удобнее для вашего случая.

Совет: Начинайте с малого — замените один цикл на map или filter. Затем добавьте иммутабельность. Постепенно вы привыкнете думать функционально.

Практические советы и лайфхаки

Вот несколько приёмов, которые помогут вам внедрить ФП в повседневную разработку:

  • Избегайте let и var. Используйте const везде, где возможно. Это заставит вас создавать новые значения, а не мутировать старые.
  • Используйте библиотеки. Lodash/fp или Ramda предоставляют готовые функциональные утилиты. Не изобретайте велосипед.
  • Разделяйте вычисления и побочные эффекты. Держите чистые функции в отдельном модуле, а работу с DOM или сетью — в другом.
  • Пишите маленькие функции. Одна функция — одна задача. Это упрощает композицию.
  • Не фанатейте. ФП — не религия. Если императивный код проще и понятнее, используйте его.

И помните: главное — читаемость и поддерживаемость кода, а не следование парадигме ради парадигмы.

Что в итоге

Функциональное программирование в JavaScript — это мощный набор приёмов, который делает код чище и надёжнее. Чистые функции, иммутабельность, функции высшего порядка и композиция — вот ваш инструментарий. Начните с малого: замените цикл на map, попробуйте не мутировать объекты. Постепенно вы почувствуете, как код становится проще. И не забывайте: цель — не следовать ФП слепо, а писать понятные программы.

Практикуйтесь, экспериментируйте и получайте удовольствие от чистого кода.

Студия WNDER