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

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(); // 11
c.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 &lt;= 1 ? n : f(n - 1) + f(n - 2);
});
fib(40); // быстро благодаря кэшу
// Ловушка: closure захватывает ССЫЛКУ на переменную, не значение
function makeAdders() {
const adders = [];
for (var i = 0; i &lt; 3; i++) {
adders.push(x =&gt; x + i); // все три захватили одно и то же i
}
return adders;
}
makeAdders()[0](10); // 13 (не 10!) — i = 3 после завершения цикла
// Решение 1: let — новый binding на каждую итерацию
// for (let i = 0; ...)
// Решение 2: IIFE для создания нового scope
adders.push(((j) =&gt; x =&gt; x + j)(i)); // j захватывает текущее значение

Итог: Closure — не “снимок переменных”, а живая ссылка на scope. Изменение переменной в scope после создания closure будет видно в closure.