Парадигмы

Парадигма программирования — стиль и подход к написанию кода. JavaScript — мультипарадигменный язык: поддерживает императивный, объектно-ориентированный и функциональный стили.

Зачем нужно

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

Где используется

Везде. React тяготеет к функциональному стилю, Angular — к ООП, Node.js middleware — к функциональным паттернам. Знание парадигм позволяет осознанно выбирать стиль.

Предпосылки

Function Declaration, Классы

Императивный стиль

Описывает как делать — пошаговые инструкции:

// Найти сумму чётных чисел
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

let sum = 0;
for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    sum += numbers[i];
  }
}
console.log(sum); // 30

Декларативный стиль

Описывает что нужно получить:

const sum = numbers
  .filter(n => n % 2 === 0)
  .reduce((acc, n) => acc + n, 0);

console.log(sum); // 30

Объектно-ориентированный стиль

Данные и поведение объединены в объекты:

class ShoppingCart {
  #items = ;

  addItem(item) {
    this.#items.push(item);
    return this;
  }

  removeItem(id) {
    this.#items = this.#items.filter(item => item.id !== id);
    return this;
  }

  get total {
    return this.#items.reduce((sum, item) => sum + item.price * item.qty, 0);
  }

  get itemCount {
    return this.#items.reduce((sum, item) => sum + item.qty, 0);
  }
}

const cart = new ShoppingCart();
cart.addItem({ id: 1, name: 'Книга', price: 500, qty: 2 })
    .addItem({ id: 2, name: 'Ручка', price: 50, qty: 5 });

console.log(cart.total);     // 1250
console.log(cart.itemCount); // 7

Функциональный стиль

Данные и функции разделены, состояние не мутируется:

// Данные — простые структуры
const cart = { items:  };

// Функции — чистые, без побочных эффектов
const addItem = (cart, item) => ({
  ...cart,
  items: [...cart.items, item]
});

const removeItem = (cart, id) => ({
  ...cart,
  items: cart.items.filter(item => item.id !== id)
});

const getTotal = (cart) =>
  cart.items.reduce((sum, item) => sum + item.price * item.qty, 0);

// Использование — каждая операция возвращает новый объект
const cart1 = addItem(cart, { id: 1, name: 'Книга', price: 500, qty: 2 });
const cart2 = addItem(cart1, { id: 2, name: 'Ручка', price: 50, qty: 5 });
const cart3 = removeItem(cart2, 1);

console.log(getTotal(cart2)); // 1250
console.log(getTotal(cart3)); // 250
console.log(cart.items);      //  — оригинал не изменился

Сравнение парадигм

Аспект Императивный ООП Функциональный
Фокус Как делать Объекты и сообщения Преобразование данных
Состояние Мутируемое Инкапсулированное Иммутабельное
Побочные эффекты Везде Контролируемые Минимизированы
Переиспользование Процедуры Наследование Композиция
Тестирование Сложнее Средне Проще
Типичный пример Алгоритмы Модели, UI Обработка данных

JavaScript — мультипарадигменный

// Одна задача — три стиля

// Императивный
function getActiveUsersImperative(users) {
  const result = ;
  for (const user of users) {
    if (user.active) {
      result.push(user.name.toUpperCase());
    }
  }
  result.sort();
  return result;
}

// ООП
class UserCollection {
  constructor(users) { this.users = users; }
  active {
    return new UserCollection(this.users.filter(u => u.active));
  }
  names { return this.users.map(u => u.name.toUpperCase()); }
  sorted {
    return new UserCollection(
      [...this.users].sort((a, b) => a.name.localeCompare(b.name))
    );
  }
  toArray { return this.users; }
}

// Функциональный
const getActiveUsersFP = (users) =>
  users
    .filter(u => u.active)
    .map(u => u.name.toUpperCase())
    .sort();

Когда какой стиль

Задача Рекомендуемый стиль
Обработка коллекций данных Функциональный
Сложная бизнес-логика с состоянием ООП
Низкоуровневые алгоритмы Императивный
React-компоненты Функциональный
Node.js middleware Функциональный
Игровые объекты ООП
Утилиты и хелперы Функциональный
DOM-манипуляции Императивный/ООП

Частые ошибки

1. Догматичный выбор одной парадигмы

// Не нужно всё делать функционально:
// Иногда for-цикл понятнее цепочки map/filter/reduce

// Не нужно всё оборачивать в классы:
// Простая функция лучше класса с одним методом

2. Смешение стилей без причины

// Плохо — мутация в "функциональной" цепочке
const result = data
  .filter(x => { sideEffect; return x > 0; }) // побочный эффект!
  .map(x => x * 2);

// Хорошо — чистые функции в цепочке
const result = data
  .filter(x => x > 0)
  .map(x => x * 2);

Практика

  1. Реши одну задачу (найти максимальный элемент) в трёх стилях
  2. Перепиши императивный код с циклами на функциональный с map/filter/reduce
  3. Создай простую модель данных в ООП и ФП стилях — сравни
  4. Определи, какой стиль используется в 5 фрагментах кода
  5. Напиши утилиту обработки данных в функциональном стиле с pipe/compose

Связанные темы

Ресурсы