Всегда ли замыкание в JavaScript хранит один и тот же экземпляр внешних данных
Что проверяют
Понимаете ли вы, что замыкание держит не значение и не копию, а ссылку на лексическое окружение, и когда это окружение одно на всех, а когда их несколько. Следом часто спрашивают про цикл с var и про «устаревшие замыкания» в React.
Как отвечать
Короткий ответ: нет, не всегда. Какой экземпляр данных увидит замыкание, определяется тем, в каком вызове функции оно создано.
- каждый вызов внешней функции: создаёт новое окружение. Два счётчика
const a = outer(); const b = outer();живут независимо, у каждого свояcount; - замыкания из одного вызова: делят одно окружение. Если
outerвернёт сразуincиget, обе функции работают с одной переменной, и изменение через одну видно другой; varв цикле: одна привязка на всю функцию, поэтому все колбэки увидят последнее значениеi;letв заголовкеfor: на каждой итерации создаётся новая привязка с текущим значением, поэтому колбэки получают 0, 1, 2;- объект во внешней переменной: замыкание видит и переприсваивание переменной, и мутации объекта, потому что держит привязку, а не снимок.
Для фронтенда главный практический пример это React. Каждый рендер компонента это новый вызов функции, и обработчик, созданный в рендере, видит state именно этого рендера. Отсюда классика: setInterval внутри useEffect с пустым массивом зависимостей навсегда видит начальное значение. Лечится функциональным обновлением setCount(c => c + 1), честным списком зависимостей или useRef.
Можно добавить про память: в V8 замыкания из одной области обычно делят один объект контекста, поэтому большая переменная, нужная лишь одному колбэку, живёт, пока жив любой из них.
Где спотыкаются
Говорят, что замыкание копирует значение в момент создания. С такой моделью пример с var в цикле объяснить не получается.
Приписывают эффект let самому замыканию. Замыкание работает одинаково в обоих случаях, разница в том, сколько привязок создаёт цикл.
Не связывают теорию с React и не могут объяснить, почему обработчик внутри эффекта показывает старое состояние, хотя на экране давно новое.