JS Tower
JavaScript & TypeScriptМодуль 6: ООП и паттерны

Чистые функции

Иммутабельность и функциональный подход

Цель урока

В этом уроке ты научишься:

  • Понимать чистые функции
  • Работать с иммутабельными данными
  • Избегать побочных эффектов

Что такое чистая функция

Чистая функция:

  1. При одинаковых входных данных возвращает одинаковый результат
  2. Не имеет побочных эффектов
// Чистая
function add(a, b) {
  return a + b;
}

// Нечистая — зависит от внешней переменной
let multiplier = 2;
function multiply(n) {
  return n * multiplier;
}

// Нечистая — побочный эффект
function addToArray(arr, item) {
  arr.push(item); // изменяет входной массив
  return arr;
}

Побочные эффекты

Что считается побочным эффектом:

  • Изменение входных параметров
  • Изменение глобальных переменных
  • Запись в консоль, файл, БД
  • HTTP-запросы
  • Работа с DOM
// С побочным эффектом
let total = 0;
function addToTotal(n) {
  total += n; // изменяет внешнюю переменную
}

// Без побочного эффекта
function calculateTotal(numbers) {
  return numbers.reduce((sum, n) => sum + n, 0);
}

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

Не изменяй данные — создавай новые.

Массивы

// Плохо — мутация
function addItem(arr, item) {
  arr.push(item);
  return arr;
}

// Хорошо — новый массив
function addItem(arr, item) {
  return [...arr, item];
}

// Удаление
function removeItem(arr, index) {
  return [...arr.slice(0, index), ...arr.slice(index + 1)];
}

// Обновление
function updateItem(arr, index, newValue) {
  return arr.map((item, i) => i === index ? newValue : item);
}

Объекты

// Плохо — мутация
function updateUser(user, name) {
  user.name = name;
  return user;
}

// Хорошо — новый объект
function updateUser(user, name) {
  return { ...user, name };
}

// Вложенные объекты
function updateAddress(user, city) {
  return {
    ...user,
    address: {
      ...user.address,
      city
    }
  };
}

Преимущества чистых функций

  1. Предсказуемость — легко понять, что делает функция
  2. Тестируемость — не нужно настраивать окружение
  3. Кэширование — можно мемоизировать результаты
  4. Параллелизм — нет общих данных
// Легко тестировать
function calculateDiscount(price, percent) {
  return price * (1 - percent / 100);
}

// Тест
console.log(calculateDiscount(100, 20) === 80); // true

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

const add10 = x => x + 10;
const multiply2 = x => x * 2;
const subtract5 = x => x - 5;

// Ручная композиция
const result = subtract5(multiply2(add10(5)));
console.log(result); // 25

// Функция compose
const compose = (...fns) => x => 
  fns.reduceRight((acc, fn) => fn(acc), x);

const calculate = compose(subtract5, multiply2, add10);
console.log(calculate(5)); // 25

// Функция pipe (слева направо)
const pipe = (...fns) => x => 
  fns.reduce((acc, fn) => fn(acc), x);

const calculate2 = pipe(add10, multiply2, subtract5);
console.log(calculate2(5)); // 25

Практика

Задание 1: Чистая функция

Задача: Преобразуй функцию в чистую.

Запустите код для проверки
Loading...
Ваш вывод:
Ожидаемый результат:
["a","b"]

Задание 2: Иммутабельное обновление

Задача: Обнови объект без мутации.

Запустите код для проверки
Loading...
Ваш вывод:
Ожидаемый результат:
{"name":"Иван","age":26}
{"name":"Иван","age":25}

Задание 3: Композиция

Задача: Создай цепочку преобразований.

Запустите код для проверки
Loading...
Ваш вывод:
Ожидаемый результат:
49

Проверь себя

  1. Что такое чистая функция?
  2. Почему мутация — это плохо?
  3. Как обновить вложенный объект иммутабельно?