Замыкания: паттерны на практике
Замыкания на практике: счётчики, модуль, мемоизация и настоящая приватность данных.
Замыкание (closure) — функция вместе с захваченными переменными окружения, в котором она была создана. Эти переменные живут, пока жива функция.
Счётчик: переменная живёт между вызовами
Локальная переменная обычно умирает с завершением функции. Но если вернуть вложенную функцию, которая на неё ссылается, переменная сохранится — это и есть замыкание. Каждый вызов фабрики создаёт отдельное окружение:
function createCounter() {
let count = 0;
return {
increment() { count++; return count; },
decrement() { count--; return count; },
value() { return count; }
};
}
const c = createCounter();
console.log(c.increment());
console.log(c.increment());
console.log(c.decrement());
console.log("итог:", c.value());Вывод:
1 2 1 итог: 1
Паттерн «Модуль»: приватное и публичное
Замыкание — основа модульного паттерна: внутри функции прячем приватное состояние и возвращаем только публичный API. Снаружи к count не дотянуться — нет ссылки.
Мемоизация: кэш в замыкании
Мемоизация ускоряет дорогие чистые функции, запоминая результат для каждого аргумента. Кэш живёт в замыкании и не виден снаружи:
function memoize(fn) {
const cache = new Map();
return function(n) {
if (cache.has(n)) {
console.log("из кэша:", n);
return cache.get(n);
}
const result = fn(n);
cache.set(n, result);
return result;
};
}
const square = memoize(n => n * n);
console.log(square(4));
console.log(square(4));
console.log(square(5));Вывод:
16 из кэша: 4 16 25
Второй вызов square(4) не пересчитывал, а взял из кэша — отсюда строка «из кэша: 4».
Приватность через WeakMap
До приватных полей (#field) данные класса прятали в замыкании или в WeakMap, где ключ — сам экземпляр. Снаружи такие данные недоступны:
const _balance = new WeakMap();
class Account {
constructor(initial) { _balance.set(this, initial); }
deposit(sum) { _balance.set(this, _balance.get(this) + sum); }
getBalance() { return _balance.get(this); }
}
const acc = new Account(100);
acc.deposit(50);
console.log("баланс:", acc.getBalance());
console.log("приватно? напрямую не достать:", acc.balance);Вывод:
баланс: 150 приватно? напрямую не достать: undefined
Классическая ловушка с циклом
Знаменитый баг: var в цикле создаёт одну переменную на все итерации, поэтому все колбэки видят её финальное значение. Решение — let, который создаёт новую привязку на каждой итерации.
Итог
- Замыкание сохраняет переменные окружения функции после её создания.
- На замыканиях строятся счётчики, модули, мемоизация и приватность.
- В циклах используйте
let, а неvar, чтобы каждый колбэк захватил своё значение.