Closures (замыкания)
В одной фразе: closure — функция, связанная со своим лексическим окружением, которое “живёт” столько, сколько существует сама функция, даже после того как внешняя функция завершила выполнение. На собесе ждут примеры: module pattern, мемоизация, каррирование — и классическую ловушку с var в цикле.
makeCounter() [env: count=0] <— lexical env | +—> increment() captures: count (by ref) +—> decrement() same env, same count +—> getCount() reads count count persists as long as any of these functions live
// Module pattern через closure: инкапсуляция состоянияfunction makeCounter(initial = 0) { let count = initial; // приватное состояние в closure return { increment() { return ++count; }, decrement() { return --count; }, reset() { count = initial; }, getCount() { return count; }, };}const c = makeCounter(10);c.increment(); // 11c.increment(); // 12// count недоступен снаружи напрямую — настоящая инкапсуляция// Мемоизация: closure хранит кэш между вызовамиfunction memoize(fn) { const cache = new Map(); // cache живёт в closure return function(...args) { const key = JSON.stringify(args); if (cache.has(key)) return cache.get(key); const result = fn.apply(this, args); cache.set(key, result); return result; };}
const fib = memoize(function f(n) { return n <= 1 ? n : f(n - 1) + f(n - 2);});fib(40); // быстро благодаря кэшу// Ловушка: closure захватывает ССЫЛКУ на переменную, не значениеfunction makeAdders() { const adders = []; for (var i = 0; i < 3; i++) { adders.push(x => x + i); // все три захватили одно и то же i } return adders;}makeAdders()[0](10); // 13 (не 10!) — i = 3 после завершения цикла
// Решение 1: let — новый binding на каждую итерацию// for (let i = 0; ...)
// Решение 2: IIFE для создания нового scopeadders.push(((j) => x => x + j)(i)); // j захватывает текущее значениеИтог: Closure — не “снимок переменных”, а живая ссылка на scope. Изменение переменной в scope после создания closure будет видно в closure.