Архитектурные паттерны в JavaScript: как не утонуть в коде

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

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

Начать курс

Почему архитектура — это не про скучные схемы

Представьте, что вы строите дом. Можно просто навалить кирпичи, залить бетон и надеяться, что конструкция не рухнет. А можно спроектировать фундамент, несущие стены и перекрытия. В JavaScript-разработке то же самое: без продуманной архитектуры ваш проект превращается в "спагетти-код", где правка одной функции ломает всё приложение.

Архитектурные паттерны — это не сухая теория из книжек. Это проверенные боевые схемы, которые помогут вам:

  • Не сойти с ума при разрастании проекта до 50 000 строк кода.
  • Быстро находить баги, потому что код разделён на логические блоки.
  • Добавлять фичи без страха что-то сломать.

В этой статье мы разберём пять ключевых паттернов для JavaScript-приложений: от классического MVC до модульного подхода. Поехали!

MVC (Model-View-Controller): дедушка всех паттернов

MVC — это золотой стандарт веб-разработки. Паттерн делит приложение на три слоя:

  • Model — данные и бизнес-логика (CRUD, валидация).
  • View — интерфейс, который видит пользователь (HTML, рендер).
  • Controller — посредник: принимает запросы пользователя и обновляет Model или View.

Вот как это выглядит в коде:

// Model
class UserModel {
    constructor() {
        this.users = [];
    }

    addUser(user) {
        this.users.push(user);
    }

    getUsers() {
        return this.users;
    }
}

// View
class UserView {
    render(users) {
        const list = document.getElementById('user-list');
        list.innerHTML = users.map(u => `<li>${u.name}</li>`).join('');
    }
}

// Controller
class UserController {
    constructor(model, view) {
        this.model = model;
        this.view = view;
    }

    addUser(name) {
        this.model.addUser({ name, id: Date.now() });
        this.view.render(this.model.getUsers());
    }
}

// Использование
const model = new UserModel();
const view = new UserView();
const controller = new UserController(model, view);
controller.addUser('Алиса');
controller.addUser('Боб');

Когда использовать MVC в JavaScript? Идеально для средних и крупных SPA (одностраничных приложений). Но будьте осторожны: если View сильно зависит от Model, паттерн превращается в "Massive View Controller" — контроллер раздувается до неприличия.

MVP (Model-View-Presenter): строгий надзиратель

MVP — эволюция MVC, где Presenter берёт на себя всю логику управления View. В отличие от MVC, View в MVP — пассивная "тупая" прослойка. Она только отображает данные и передаёт события Presenter'у.

// Model (та же, что и в MVC)
class UserModel {
    constructor() {
        this.users = [];
    }
    addUser(user) { this.users.push(user); }
    getUsers() { return this.users; }
}

// View — пассивный интерфейс
class UserView {
    constructor() {
        this.list = document.getElementById('user-list');
        this.addButton = document.getElementById('add-btn');
    }

    bindAddUser(handler) {
        this.addButton.addEventListener('click', () => {
            const name = prompt('Введите имя:');
            if (name) handler(name);
        });
    }

    render(users) {
        this.list.innerHTML = users.map(u => `<li>${u.name}</li>`).join('');
    }
}

// Presenter — мозг приложения
class UserPresenter {
    constructor(model, view) {
        this.model = model;
        this.view = view;
        this.view.bindAddUser(this.handleAddUser.bind(this));
        this.view.render(this.model.getUsers());
    }

    handleAddUser(name) {
        this.model.addUser({ name, id: Date.now() });
        this.view.render(this.model.getUsers());
    }
}

// Запуск
const presenter = new UserPresenter(new UserModel(), new UserView());

Плюсы MVP: View можно легко тестировать изолированно. Минусы: Presenter часто становится "божественным объектом", который знает всё.

MVVM (Model-View-ViewModel): реактивность на стероидах

MVVM — это то, на чём построены Vue.js, Knockout и частично React. Главная фишка — двустороннее связывание данных (two-way data binding). ViewModel автоматически синхронизирует Model и View.

// Простая реализация MVVM через Proxy (ES6)
function createViewModel(model, view) {
    return new Proxy(model, {
        set(target, property, value) {
            target[property] = value;
            view.update(property, value); // Авто-обновление View
            return true;
        }
    });
}

// Model
const userModel = { name: 'Алиса', age: 25 };

// View
const userView = {
    update(property, value) {
        const el = document.getElementById(`user-${property}`);
        if (el) el.textContent = value;
    }
};

// ViewModel
const vm = createViewModel(userModel, userView);

// Теперь изменения в vm.name автоматически обновляют DOM
vm.name = 'Боб'; // DOM обновится без ручного вызова

Совет: Не пишите свой MVVM с нуля — используйте готовые фреймворки. Но понимать механику полезно: это знание спасёт вас, когда Vue.js начнёт "чудить" в сложных сценариях.

Module Pattern: спасатель от глобальной грязи

До ES6 модули в JavaScript были "изолентой и палками". Паттерн "Модуль" (Module Pattern) решает проблему глобальных переменных и конфликтов имён. Это классика, которая до сих пор работает.

// Модуль через IIFE (Immediately Invoked Function Expression)
const UserModule = (function() {
    // Приватные переменные (недоступны снаружи)
    let users = [];
    const API_KEY = 'secret-123';

    // Приватная функция
    function validateUser(user) {
        return user.name && user.name.length > 2;
    }

    // Публичный API
    return {
        addUser(user) {
            if (validateUser(user)) {
                users.push(user);
                console.log('Пользователь добавлен');
            } else {
                console.error('Некорректное имя');
            }
        },
        getUsers() {
            return [...users]; // Возвращаем копию, а не ссылку
        },
        getApiKey() {
            return API_KEY;
        }
    };
})();

// Использование
UserModule.addUser({ name: 'Виктор' }); // "Пользователь добавлен"
console.log(UserModule.users); // undefined — приватно!

Когда применять: В проектах без сборщиков (Webpack, Vite) или для изоляции кода в legacy-системах. В современном JS лучше использовать import/export, но понимание Module Pattern всё равно нужно — вы встретите его в старых кодовых базах.

Observer (Наблюдатель): когда всем нужно знать об изменениях

Паттерн Observer — основа событийной архитектуры. Один объект (Subject) уведомляет всех подписчиков (Observers) о своих изменениях. Без этого паттерна не обходится ни один event loop в браузере.

// Subject — за кем наблюдают
class NewsA                

Блоги

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