Найти тему
Дмитрий Аюров

Замыкание

Замыкание - это функция, плюс ее область видимости.

Замыкание
Замыкание

Контекст выполнения

JavaScript - однопоточный язык. Это означает, что у него один стек вызовов и в один момент времени он может выполнять только одну задачу. Новая задача или запрос на выполнение всегда будет сверху стека. Как только запущенная функция завершится, ее контекст выкидывается из стека, запустив контекст выполнения, который стоит ниже в очереди.

Стек
Стек

Динамическая и Лексическая область видимости

Область видимости - это способ где искать переменную. Динамическая область видимости ищет функцию там, где она вызывается. Лексическая, где функция была объявлена. Окружение для области видимости — это доступная из текущей области видимости структура данных.

Лексическая и динамическая область видиости
Лексическая и динамическая область видиости

Для динамической области видимости есть только глобальное окружение, и функция, возвращаемая из dynamicScope, найдет глобальную переменную. В случае c лексической области видимости, внутренняя функция помнит, где она была объявлена. В данном случае, она помнит, что в ее окружении есть не только глобальное, но и лексическое окружение родительской внешней функции lexicalScope(). Поэтому, именно в ней сначала происходит поиск, и если бы нужная переменная отсутствовала, поиск переключился бы в глобальную область видимости и нашел ее там.

Лексическое окружение

При выполнении функции или глобального кода создается свое лексическое окружение. У лексического окружения есть два компонента: запись в окружении (место хранения объявлений переменных и функций) и отсылка к внешнему окружению (означает то, что у него есть доступ к внешнему/родительскому окружению). Все функции помнят лексическое окружение, в котором они были созданы. Они имеют скрытое свойство [[Environment]], которое хранит ссылку на лексическое окружение, в котором была создана функция.

Глобальное лексическое окружение и лексическое окружение функции "first()"
Глобальное лексическое окружение и лексическое окружение функции "first()"

Замыкание

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

Каждая функция имеет доступ к лексическому окружению, в котором она была создана. Это и есть замыкание. Функция замыкает в себе переменные и аргументы функции, внутри которой определена, запоминает, где она была объявлена вместе с объектом лексического окружения.

То есть контекст внешней функции удаляется из стека, а его лексическое окружение остается, так как лексическое окружение внутренней, через [[Environment]], ссылается к переменным и аргументам внешней.

Контекст "getCounter()" удаляется, но его лексическое окружение в виде переменной "counter" остается.
Контекст "getCounter()" удаляется, но его лексическое окружение в виде переменной "counter" остается.

Циклы и замыкание

-7

В данном примере, ожидается, что цикл for выведет числа от 1 до 5. Однако, код выводит "6" пять раз подряд через односекундные интервалы.

Почему так?

Все итерации цикла замыкаются на одну и ту же глобальную область видимости, и для каждой settimeout раздается одна единственная переменная i, которая по завершению цикла равна 6.

Чтобы решить данную проблему необходимо создать новую изолированную область видимости для каждой итерации цикла:

-8

Теперь на каждой итерации цикла создается своя область видимости, из которой settimeout имеет возможность захватывать i с правильным итерационным значением.

-9

Есть еще возможность использовать ключевое слово let для привязки переменных к блочной области видимости цикла for. Разница с использованием var в заголовке цикла, заключается в том, что переменные объявленные через var, будут видны во всей функции, в которой находится этот цикл. При let, на каждой итерации создается новая i с обновленным значением, которая выдается одному settimeout и отправляет его в стек.