Может ли быть несколько экземпляров лексического окружения замыкания в JavaScript
Что проверяют
Различаете ли вы момент создания функции и момент её вызова. Именно вызов порождает новое лексическое окружение, а функция лишь запоминает ссылку на то, в котором её создали. Следом обычно дают задачу с var и let в цикле с setTimeout или спрашивают, сколько памяти держит замыкание.
Как отвечать
Да, и это обычная ситуация. Опорные факты:
- окружение создаётся при вызове: каждый запуск функции создаёт свою запись окружения с параметрами и локальными переменными. Два вызова
createCounter()дают два независимыхcount; - функция хранит ссылку на окружение создания: в спецификации это внутренний слот
[[Environment]]. Поэтому замыкание видит переменные даже после того, как внешняя функция вернула управление; - функции из одного вызова делят одно окружение: если
createCounter()возвращает объект сincиdec, оба метода работают с одним и тем жеcount; - блок тоже создаёт окружение:
letиconstвнутри{}живут в своём окружении, а в циклеfor (let i ...)на каждую итерацию заводится новая привязкаi. Сvarпеременная одна на всю функцию, и все колбэки из цикла увидят последнее значение.
Окружения выстраиваются в цепочку: поиск имени идёт от текущего к внешним, вплоть до глобального. Окружение живёт, пока на него ссылается хотя бы одна функция, и сборщик мусора освобождает его вместе с последней такой функцией.
Хорошо добавить деталь про V8: переменные, захваченные любым вложенным замыканием, лежат в общем объекте контекста. Если одно замыкание ссылается на большой массив, а другое из той же функции живёт долго, массив тоже не освободится.
Где спотыкаются
Говорят, что окружение создаётся при объявлении функции. Тогда непонятно, откуда у двух счётчиков разные значения, и ответ разваливается на первом же примере.
Путают копию и ссылку: считают, что замыкание сохраняет значения переменных на момент создания. На деле оно видит текущее значение, отсюда классический вывод 3, 3, 3 в цикле с var.
Не связывают замыкания с утечками. Обработчик события или таймер, который забыли снять, держит всё окружение со всеми данными, и в SPA это копится при каждом переходе между экранами.