Замыкание (Closure)
Замыкание - это функция, которая помнит переменные из внешней области видимости даже после того, как эта область завершила выполнение.
Как это работает
В JavaScript каждая функция при создании «захватывает» ссылку на лексическое окружение, в котором она была объявлена. Это окружение хранит переменные и остаётся живым, пока на него есть хотя бы одна ссылка.
function makeCounter() {
let count = 0 // переменная во внешней функции
return function() {
count++ // внутренняя функция «помнит» count
return count
}
}
const counter = makeCounter()
console.log(counter()) // 1
console.log(counter()) // 2
console.log(counter()) // 3
После вызова makeCounter() её стек-фрейм уничтожается, но переменная count остаётся живой, потому что возвращённая функция держит на неё ссылку.
Практическое применение
Инкапсуляция и приватные переменные:
function createUser(name) {
let _score = 0 // «приватное» поле
return {
getName: () => name,
addScore: (n) => { _score += n },
getScore: () => _score,
}
}
const user = createUser("Алиса")
user.addScore(10)
console.log(user.getScore()) // 10
// _score недоступна снаружи напрямую
Частичное применение (partial application):
function multiply(a) {
return (b) => a * b
}
const double = multiply(2)
const triple = multiply(3)
console.log(double(5)) // 10
console.log(triple(5)) // 15
Классическая ловушка: замыкание в цикле
// ❌ Проблема - все кнопки выведут 3
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}
// ✅ Решение 1: let создаёт новую переменную на каждую итерацию
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}
// ✅ Решение 2: IIFE создаёт новое замыкание
for (var i = 0; i < 3; i++) {
;(function(j) {
setTimeout(() => console.log(j), 100)
})(i)
}
Утечки памяти
Замыкания могут случайно удерживать большие объекты в памяти. Если вы замкнули ссылку на DOM-элемент или большой массив, а сама функция живёт долго - сборщик мусора (GC, Garbage Collector) не сможет освободить память. Решение - присвоить null после использования.
Резюме
- Замыкание создаётся каждый раз, когда объявляется функция внутри другой функции.
- Функция хранит ссылку на переменную, а не её копию - поэтому изменения видны.
- Используется для инкапсуляции, каррирования, мемоизации, обработчиков событий.