Как устроен паттерн «Модуль» в JavaScript: инкапсуляция через замыкания и ES-модули?
Структурные паттерны
«Модуль» прячет детали реализации и открывает только публичный API: исторически — через IIFE и замыкание, сегодня — через ES-модули, где всё неэкспортированное приватно.
IIFE и Revealing Module
- До ES2015 в браузере не было модулей: всё попадало в глобальную область, скрипты конфликтовали именами.
(function () { ... })()создаёт замыкание, возвращаемый объект — публичный API.- Revealing Module: всё объявлено приватно, в конце возвращается объект со ссылками на нужные функции.
ES-модули
- Статические
import/export→ анализ зависимостей до выполнения и tree shaking. - Модуль исполняется один раз и кэшируется, поэтому его состояние — фактически синглтон в пределах приложения.
- Импорты — живые привязки только для чтения: изменения внутри модуля видны снаружи, присвоить импорту нельзя. В CommonJS
const { count } = require()копирует значение.
// counter.js
let count = 0; // не экспортируем напрямую — снаружи не изменить
export function increment() {
return ++count;
}
export { count };
// main.js
import { count, increment } from './counter.js';
increment();
console.log(count); // 1 — live bindingПодводные камни
- Состояние модуля — скрытый глобал: мешает тестам (
jest.resetModules) и SSR, где один модуль обслуживает запросы разных пользователей. - Циклические импорты: обращение к
let/constдо инициализации даёт ReferenceError (TDZ).
Коротко для собеседования
Модуль открывает наружу только нужный API, а остальное держит приватным: раньше через IIFE и замыкание, сейчас через ES-модули. Состояние модуля живёт в одном экземпляре на приложение, поэтому для независимых копий экспортируют фабрику, а не готовое состояние.