Перейти к содержимому

87. Модульный паттерн (IIFE)

Модульный паттерн позволяет создавать инкапсулированные модули с приватными переменными и публичным API — ещё до появления class и ES Modules. Основан на IIFE (Immediately Invoked Function Expression) и замыканиях.

IIFE (Immediately Invoked Function Expression) — функция, которая вызывается сразу после объявления:

// Классический IIFE
;(function() {
const secret = 'приватная переменная'
console.log(secret) // доступна внутри
})()
// console.log(secret) // ReferenceError — снаружи недоступна
// Стрелочный IIFE
;(() => {
const local = 42
console.log(local)
})()

IIFE создаёт изолированную область видимости — переменные внутри не загрязняют глобальное пространство.

const counter = (function() {
// Приватное состояние
let count = 0
// Публичный API (возвращаемый объект)
return {
increment() { count++ },
decrement() { count-- },
reset() { count = 0 },
getCount() { return count },
}
})()
counter.increment()
counter.increment()
counter.increment()
counter.decrement()
console.log(counter.getCount()) // 2
console.log(counter.count) // undefined — приватная!

«Раскрывающий» модульный паттерн: всё определяется приватно, а в конце явно «раскрываются» нужные функции:

const userModule = (function() {
// Приватные переменные
let users = []
let nextId = 1
// Приватные функции
function findById(id) {
return users.find(u => u.id === id)
}
function validate(name, email) {
return name.length >= 2 && email.includes('@')
}
// Публичные функции (те же функции, но раскрытые)
function addUser(name, email) {
if (!validate(name, email)) throw new Error('Некорректные данные')
const user = { id: nextId++, name, email }
users.push(user)
return user
}
function removeUser(id) {
const index = users.findIndex(u => u.id === id)
if (index === -1) throw new Error('Пользователь не найден')
return users.splice(index, 1)[0]
}
function getUser(id) {
return findById(id) ? { ...findById(id) } : null // возвращаем копию
}
function getAll() {
return [...users] // возвращаем копию массива
}
// Revealing: явно раскрываем только нужное
return { addUser, removeUser, getUser, getAll }
})()
const u1 = userModule.addUser('Алексей', '[email protected]')
const u2 = userModule.addUser('Настя', '[email protected]')
console.log(userModule.getAll()) // оба пользователя
console.log(userModule.getUser(1)) // { id: 1, name: 'Алексей', ... }
// userModule.validate // undefined — скрыта
// userModule.findById // undefined — скрыта

Модуль с настройками:

function createLogger(prefix = '[LOG]', { timestamps = true } = {}) {
// Приватное состояние
const logs = []
function formatMessage(level, msg) {
const time = timestamps ? `[${new Date().toISOString()}]` : ''
return `${prefix} ${time} [${level}] ${msg}`
}
return {
log(msg) { const m = formatMessage('INFO', msg); logs.push(m); console.log(m) },
warn(msg) { const m = formatMessage('WARN', msg); logs.push(m); console.warn(m) },
error(msg) { const m = formatMessage('ERROR', msg); logs.push(m); console.error(m) },
getHistory() { return [...logs] },
clear() { logs.length = 0 },
}
}
const logger = createLogger('[APP]', { timestamps: false })
logger.log('Приложение запущено')
logger.warn('Осторожно!')
logger.error('Ошибка подключения')
console.log('История:', logger.getHistory().length, 'записей')

Группировка функций в пространстве имён:

const MyApp = MyApp || {}
MyApp.utils = (function() {
function formatDate(date) {
return new Intl.DateTimeFormat('ru-RU').format(date)
}
function truncate(str, len = 50) {
return str.length <= len ? str : str.slice(0, len) + '...'
}
return { formatDate, truncate }
})()
MyApp.api = (function() {
const BASE_URL = 'https://api.example.com'
async function get(endpoint) {
const response = await fetch(`${BASE_URL}${endpoint}`)
return response.json()
}
return { get }
})()
console.log(MyApp.utils.formatDate(new Date()))
console.log(MyApp.utils.truncate('Очень длинная строка которая будет обрезана', 20))
КритерийModule PatternES Modules
Поддержка браузеровВездеСовременные браузеры
ПриватностьЧерез замыканиеТолько в модуле
СинтаксисСложнееЧище, стандартный
Tree shakingНетДа
Когда использоватьLegacy, нет сборщикаСовременные проекты
  1. Реализуйте EventEmitter как Revealing Module Pattern с методами on, off, emit.
  2. Создайте модуль shoppingCart с приватным массивом товаров и публичными методами add, remove, total.
  3. Напишите IIFE-обёртку для работы с localStorage, которая автоматически сериализует/десериализует JSON.