Skip to Content
JavaScriptЗамыкания

Замыкания

Уровень: middle.

Вопрос

Что такое замыкание и почему цикл с var и setTimeout печатает одно и то же число?

Короткий ответ

Замыкание — это функция вместе с лексическим окружением, в котором она была создана. Внутренняя функция продолжает читать внешние переменные после того, как внешний вызов завершился.

for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0) } // 3, 3, 3

var одна на всю функцию. К моменту колбэков цикл уже закончился, i === 3. С let на каждой итерации своя привязка, и в консоли будет 0, 1, 2.

Разбор

Область видимости в JS лексическая: её задаёт место объявления, а не место вызова. Замыкание хранит ссылку на переменную, а не снимок значения. Если внешнюю let-переменную потом изменить, внутренние функции увидят новое значение.

Практические места: фабрика с приватным состоянием, обработчик, который помнит id, debounce, который помнит таймер, частично применённая функция.

function createCounter() { let value = 0 return () => ++value }

Модуль ES тоже замыкает свои let и const: снаружи их нет, пока вы сами не вернёте функцию или объект.

На что смотрят на senior

Замыкание держит окружение живым. Если повесить обработчик на глобальный объект и забыть снять его, вместе с колбэком останется всё, на что он ссылается: большой массив, DOM-узел, экземпляр компонента. На разборе утечки это первый кандидат после «забыли отписаться».

В цикле рендера замыкание на каждой итерации — нормально. Проблема начинается, когда такое замыкание сохраняют в подписку без очистки.

Типичные ошибки

  • Объяснять замыкание как «функция внутри функции» без лексического окружения.
  • Думать, что замыкание копирует значение в момент создания.
  • Лечить var в цикле только через IIFE и не помнить про let.

Задача

Сколько раз функция add создаёт новое окружение и что вернёт второй вызов?

function make(step) { let total = 0 return () => { total += step return total } } const add = make(2) add() add()
Показать ответ

make(2) создаёт одно окружение с total и step. Обе стрелки — это один и тот же возвращённый функция-замыкание, она помнит total, а не копирует его.

Первый вызов вернёт 2, второй 4.

Last updated on