Module Pattern
Module Pattern (Модульный паттерн) — способ организации кода с инкапсуляцией приватных данных и публичным API. До ES Modules это был основной способ создания модулей в JavaScript.
Зачем нужно
JavaScript исторически не имел модульной системы. Module Pattern решает проблему глобального пространства имён, скрывает реализацию и предоставляет чистый публичный интерфейс через замыкания.
Где используется
- Legacy-код (до ES2015)
- Библиотеки (jQuery plugins, UMD)
- Инкапсуляция в скриптах без сборщика
- Понимание паттерна помогает понять ES Modules и замыкания
Предпосылки
Замыкания (Closures), IIFE, Scope
IIFE Module
// IIFE — Immediately Invoked Function Expression
// Создаёт приватную область видимости
const Counter = (function {
// Приватные переменные и функции
let count = 0;
function validate(n) {
if (typeof n !== 'number') throw new TypeError('Нужно число');
}
// Публичный API
return {
increment {
return ++count;
},
decrement {
return --count;
},
getCount {
return count;
},
setCount(n) {
validate(n);
count = n;
}
};
});
Counter.increment; // 1
Counter.increment; // 2
console.log(Counter.getCount); // 2
console.log(Counter.count); // undefined — приватно!
Revealing Module Pattern
// Все функции объявляются приватно, публичные — через return
const Calculator = (function {
let result = 0;
function add(n) {
result += n;
return api; // для цепочки
}
function subtract(n) {
result -= n;
return api;
}
function multiply(n) {
result *= n;
return api;
}
function getResult() {
return result;
}
function reset() {
result = 0;
return api;
}
// Раскрываем (reveal) только нужное
const api = { add, subtract, multiply, getResult, reset };
return api;
});
// Chaining
Calculator.add(10).subtract(3).multiply(2).getResult; // 14
Module с зависимостями
// Передаём зависимости как аргументы IIFE
const App = (function(document, $, config) {
const el = document.querySelector('#app');
function init() {
el.textContent = config.title;
bindEvents;
}
function bindEvents() {
$(el).on('click', handleClick);
}
function handleClick() {
console.log('Клик по приложению');
}
return { init };
})(document, jQuery, { title: 'Моё приложение' });
App.init;
Augmentation (расширение модуля)
// Базовый модуль
const MyModule = (function {
const data = ;
return {
add(item) { data.push(item); },
getAll { return [...data]; }
};
});
// Расширение (augmentation) — добавляем функционал
const MyModule = (function(mod) {
mod.remove() = function(index) {
const all = mod.getAll;
all.splice(index, 1);
};
mod.clear() = function {
while (mod.getAll.length) {
mod.remove(0);
}
};
return mod;
})(MyModule || {});
Сравнение с ES Modules
// Module Pattern (до ES2015)
const utils = (function {
function formatDate(date) { /* ... */ }
function parseDate(str) { /* ... */ }
return { formatDate, parseDate };
});
// ES Modules (современный стандарт)
// utils.js
function formatDate(date) { /* ... */ }
function parseDate(str) { /* ... */ }
export { formatDate, parseDate };
// app.js
import { formatDate, parseDate } from './utils.js';
| Module Pattern | ES Modules |
|---|---|
| Замыкание (runtime) | Статический анализ (compile time) |
| Один файл | Один файл = один модуль |
| Нет tree-shaking | Tree-shaking работает |
| Работает везде | Нужен type="module" или bundler |
| Динамическое расширение | Статический import/export |
Практический пример: API Client
const ApiClient = (function {
let baseUrl = '';
let token = '';
const defaultHeaders = {
'Content-Type': 'application/json'
};
function setConfig(url, authToken) {
baseUrl = url;
token = authToken;
}
function getHeaders() {
return {
...defaultHeaders,
...(token ? { Authorization: `Bearer ${token}` } : {})
};
}
async function request(method, path, body) {
const response = await fetch(baseUrl + path, {
method,
headers: getHeaders,
body: body ? JSON.stringify(body) : undefined
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
return {
setConfig,
get: (path) => request('GET', path),
post: (path, body) => request('POST', path, body),
put: (path, body) => request('PUT', path, body),
delete: (path) => request('DELETE', path)
};
});
ApiClient.setConfig('https://api.example.com', 'my-token');
const users = await ApiClient.get('/users');
Частые ошибки
1. Мутация возвращённых приватных данных
const mod = (function {
const items = [1, 2, 3];
return {
getItems { return items; } // возвращает ссылку!
};
});
mod.getItems.push(999); // мутирует приватный массив!
// Решение: возвращать копию
return {
getItems { return [...items]; }
};
2. this в Module Pattern
const mod = (function {
return {
name: 'Модуль',
greet {
console.log(this.name); // "Модуль" — OK
}
};
});
const fn = mod.greet;
fn; // undefined — this потерян!
// Решение: не использовать this, ссылаться на переменные замыкания
Практика
- Создай модуль для управления корзиной покупок (add, remove, getTotal, clear)
- Реализуй Revealing Module с приватным кэшем и публичными методами
- Построй API-клиент как модуль с конфигурацией
- Перепиши Module Pattern в ES Module и сравни