Почему архитектура — это не про скучные схемы
Представьте, что вы строите дом. Можно просто навалить кирпичи, залить бетон и надеяться, что конструкция не рухнет. А можно спроектировать фундамент, несущие стены и перекрытия. В 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