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

Digital-студия WNDER

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

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

Главные плюсы ФП в JavaScript:

  • Код проще тестировать и отлаживать — функции предсказуемы.
  • Меньше багов из-за случайных изменений общих данных.
  • Легче переиспользовать и комбинировать функции.
  • Код становится декларативнее и читабельнее.

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

Чистые функции — основа основ

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

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

Пример грязной функции:

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

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

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

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

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

Неизменяемость: не мутируйте данные

В функциональном стиле данные не изменяются после создания. Вместо изменения массива или объекта вы создаёте новый. В JavaScript для этого есть удобные методы.

Плохо (мутация):

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

Хорошо (создание нового массива):

const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4]; // новый массив

Для объектов используйте spread-оператор:

const user = { name: 'Alice', age: 25 };
const updatedUser = { ...user, age: 26 };

Неизменяемость особенно важна в React и Redux, но полезна и в обычном коде — она предотвращает неожиданные изменения данных.

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

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

Пример использования map для преобразования массива:

const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2); // [2, 4, 6]

filter для отбора элементов:

const numbers = [1, 2, 3, 4];
const even = numbers.filter(n => n % 2 === 0); // [2, 4]

reduce для агрегации:

const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((acc, n) => acc + n, 0); // 10

Эти методы позволяют писать декларативный код без циклов и временных переменных.

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

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

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      };
    }
  };
}

const add = (a, b) => a + b;
const curriedAdd = curry(add);
const addFive = curriedAdd(5);
console.log(addFive(3)); // 8

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

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 formatName = compose(capitalize, toLowerCase, trim);
console.log(formatName('  aLICE  ')); // 'Alice'

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

Практические советы и сравнение подходов

Вот несколько лайфхаков для внедрения ФП в ваш код:

  • Начинайте с малого: заменяйте циклы на map, filter, reduce.
  • Избегайте изменения аргументов функций — возвращайте новые значения.
  • Используйте const вместо let везде, где возможно.
  • Для сложной логики разбивайте её на маленькие чистые функции.
  • Используйте библиотеки вроде Lodash/fp или Ramda для готовых функциональных утилит.

Сравним императивный и функциональный подходы на примере задачи: получить имена пользователей старше 18 лет.

Подход Код Плюсы Минусы
Императивный
const adults = [];
for (let i = 0; i < users.length; i++) {
  if (users[i].age >= 18) {
    adults.push(users[i].name);
  }
}
Понятно новичкам Больше кода, легче ошибиться
Функциональный
const adults = users
  .filter(user => user.age >= 18)
  .map(user => user.name);
Кратко, декларативно, легко читать Требует привычки

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

Лайфхак: Если вы работаете с React, попробуйте писать компоненты как чистые функции и использовать хуки для побочных эффектов. Это сделает код предсказуемее.

Что в итоге

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

Помните: цель не в том, чтобы использовать ФП везде, а в том, чтобы писать понятный и надёжный код. Экспериментируйте, пробуйте, и вы увидите, как JavaScript заиграет новыми красками.

Студия WNDER