Что такое замыкание (closure) и где оно применяется на практике?
Короткий ответ
- Замыкание — функция плюс её лексическое окружение
- Функция помнит переменные места, где была создана
- Переменные живут, пока на них ссылается функция
- Применение: приватное состояние, счётчики, фабрики функций
- На замыканиях построены debounce, throttle, мемоизация
- Каррирование и частичное применение — тоже замыкания
- Неосторожные замыкания могут удерживать память
Замыкание — это способность функции обращаться к переменным внешней области видимости даже после завершения внешней функции, что даёт инкапсуляцию состояния.
Как сказать вслух
пример ответаЗамыкание — это когда функция запоминает переменные того места, где её создали, и может пользоваться ими позже, даже если внешняя функция уже завершилась. Простой пример — счётчик: внешняя функция хранит число, а возвращает функцию, которая его увеличивает. Снаружи к этому числу доступа нет, получается приватное состояние. На этом же принципе работают debounce и мемоизация.
Подробный ответ
Основной ответ
При создании функция сохраняет ссылку на лексическое окружение — цепочку областей видимости, в которых она была объявлена. Когда функцию вызывают позже и в другом месте, она по-прежнему разрешает имена переменных через эту цепочку. Поэтому переменные внешней функции не уничтожаются после её возврата, пока на них ссылается хотя бы одно замыкание. Это даёт инкапсуляцию: состояние доступно только через возвращённые функции. Типичные применения — счётчики и генераторы id, фабрики обработчиков, debounce/throttle (хранят id таймера), мемоизация (хранит кэш), модульный паттерн до появления ES-модулей. Обратная сторона — замыкание удерживает всё окружение от сборки мусора, что иногда приводит к утечкам, например в забытых обработчиках событий.
Ключевые моменты
- Лексическая область. Доступные переменные определяются местом объявления функции, а не местом вызова.
- Жизнь переменных. Окружение живёт, пока на него ссылается функция; это и фича, и источник утечек памяти.
- Инкапсуляция. Замыкание — стандартный способ сделать приватное состояние без классов и полей с #.
- Основа утилит. debounce, throttle, once, memoize — все хранят внутреннее состояние именно в замыкании.
Практический контекст
Интервьюер почти всегда просит не определение, а живой пример: написать makeCounter или объяснить, что выведет цикл с колбэками. В ежедневной работе замыкания везде — каждый обработчик в React-компоненте замыкает пропсы и состояние своего рендера, отсюда известная проблема «устаревшего замыкания» (stale closure) в useEffect и setInterval. Умение это объяснить отличает понимание от заученного определения.
Пример кода
function makeCounter() {
let count = 0; // приватное состояние
return () => ++count;
}
const inc = makeCounter();
inc(); // 1
inc(); // 2
const other = makeCounter();
other(); // 1 — у каждого счётчика своё окружениеЧастые ошибки
- Дают определение, но не могут написать простейший счётчик на замыкании
- Не знают, что каждый вызов внешней функции создаёт независимое окружение
- Не связывают замыкания с реальными проблемами вроде stale closure в React