--- sidebar_position: 6 --- # Область видимости переменных, замыкание Источник: [https://learn.javascript.ru/closure](https://learn.javascript.ru/closure) ## Блоки кода Если переменная объявлена внутри блока кода `{...}`, то она видна только внутри этого блока. С помощью блоков `{...}` мы можем изолировать часть кода, выполняющую свою собственную задачу, с переменными, принадлежащими только ей Для `if`, `for`, `while` и т.д. переменные, объявленные в блоке кода `{...}`, также видны только внутри ## Вложенные функции Функция называется «вложенной», когда она создаётся внутри другой функции. Она может получить доступ к внешним переменным. Вложенная функция может быть возвращена: либо в качестве свойства нового объекта (если внешняя функция создаёт объект с методами), либо сама по себе. И затем может быть использована в любом месте. Не важно где, она всё так же будет иметь доступ к тем же внешним переменным. ## Лексическое окружение #### Переменные В JavaScript у каждой выполняемой функции, блока кода `{...}` и скрипта есть связанный с ними внутренний (скрытый) объект, называемый ***лексическим окружением** `LexicalEnvironment`. Объект лексического окружения состоит из двух частей: 1. **Environment Record** – объект, в котором как свойства хранятся все локальные переменные (а также некоторая другая информация, такая как значение this). 2. Ссылка на **внешнее лексическое окружение** – то есть то, которое соответствует коду снаружи (снаружи от текущих фигурных скобок). **«Переменная» – это просто свойство специального внутреннего объекта: `Environment Record`.** \ **«Получить или изменить переменную», означает, «получить или изменить свойство этого объекта».** - Переменная – это свойство специального внутреннего объекта, связанного с текущим выполняющимся блоком/функцией/скриптом. - Работа с переменными – это на самом деле работа со свойствами этого объекта. #### Function Declaration **Разница заключается в том, что Function Declaration мгновенно инициализируется полностью.** Когда создается лексическое окружение, Function Declaration сразу же становится функцией, готовой к использованию (в отличие от `let`, который до момента объявления не может быть использован). Поэтому мы можем вызвать функцию, объявленную как Function Declaration, до самого её объявления. Такое поведение касается только Function Declaration, а не Function Expression, в которых мы присваиваем функцию переменной. #### Внутреннее и внешнее лексическое окружение Когда запускается функция, в начале ее вызова автоматически создается новое лексическое окружение для хранения локальных переменных и параметров вызова. ![](images/06-closure-1.png) В процессе вызова функции у нас есть два лексических окружения: внутреннее (для вызываемой функции) и внешнее (глобальное): - Внутреннее лексическое окружение соответствует текущему выполнению say.\ В нём находится одна переменная name, параметр функции. Мы вызываем say("John"), так что значение переменной name равно "John". - Внешнее лексическое окружение – это глобальное лексическое окружение.\ В нём находятся переменная phrase и сама функция. У внутреннего лексического окружения есть ссылка на внешнее `outer`. **Когда код хочет получить доступ к переменной – сначала происходит поиск во внутреннем лексическом окружении, затем во внешнем, затем в следующем и так далее, до глобального.** Если переменная не была найдена, это будет ошибкой в строгом режиме (`use strict`). Без строгого режима, для обратной совместимости, присваивание несуществующей переменной создаёт новую глобальную переменную с таким же именем. #### Возврат функции ```js function makeCounter() { let count = 0; return function() { return count++; }; } let counter = makeCounter(); ``` В начале каждого вызова makeCounter() создается новый объект лексического окружения, в котором хранятся переменные для конкретного запуска makeCounter. Таким образом, мы имеем два вложенных лексических окружения, как в примере выше: ![](images/06-closure-2.png) Отличие заключается в том, что во время выполнения makeCounter() создается крошечная вложенная функция, состоящая всего из одной строки: return count++. Мы ее еще не запускаем, а только создаем. Все функции помнят лексическое окружение, в котором они были созданы. Технически здесь нет никакой магии: все функции имеют скрытое свойство [[Environment]], которое хранит ссылку на лексическое окружение, в котором была создана функция: ![](images/06-closure-3.png) Таким образом, counter.[[Environment]] имеет ссылку на \{count: 0} лексического окружения. Так функция запоминает, где она была создана, независимо от того, где она вызывается. Ссылка на [[Environment]] устанавливается один раз и навсегда при создании функции. Впоследствии, при вызове counter(), для этого вызова создается новое лексическое окружение, а его внешняя ссылка на лексическое окружение берется из counter.[[Environment]]: ![](images/06-closure-4.png) Теперь, когда код внутри counter() ищет переменную count, он сначала ищет ее в собственном лексическом окружении (пустом, так как там нет локальных переменных), а затем в лексическом окружении внешнего вызова makeCounter(), где находит count и изменяет ее. ***Переменная обновляется в том лексическом окружении, в котором она существует.*** ![](images/06-closure-5.png) Если мы вызовем counter() несколько раз, то в одном и том же месте переменная count будет увеличена до 2, 3 и т.д. 💥 **Замыкания** **Замыкание** – это функция, которая запоминает свои внешние переменные и может получить к ним доступ. В некоторых языках это невозможно, или функция должна быть написана специальным образом, чтобы получилось замыкание. Но, как было описано выше, в JavaScript, все функции изначально являются замыканиями. То есть они автоматически запоминают, где были созданы, с помощью скрытого свойства `[[Environment]]`, и все они могут получить доступ к внешним переменным. Когда на собеседовании фронтенд-разработчику задают вопрос: «что такое замыкание?», – правильным ответом будет определение замыкания и объяснения того факта, что все функции в JavaScript являются замыканиями, и, может быть, несколько слов о технических деталях: свойстве `[[Environment]]` и о том, как работает лексическое окружение. Есть только одно исключение, когда функция создаётся с использованием `new Function`, в её `[[Environment]]` записывается ссылка не на внешнее лексическое окружение, в котором она была создана, а на глобальное. Поэтому такая функция имеет доступ только к глобальным переменным. ## Сборка мусора Обычно лексическое окружение удаляется из памяти вместе со всеми переменными после завершения вызова функции. Это связано с тем, что на него нет ссылок. Как и любой объект JavaScript, оно хранится в памяти только до тех пор, пока к нему можно обратиться. Однако если существует вложенная функция, которая все еще доступна после завершения функции, то она имеет свойство `[[Environment]]`, ссылающееся на лексическое окружение. В этом случае лексическое окружение остается доступным даже после завершения работы функции. Объект лексического окружения исчезает, когда становится недоступным (как и любой другой объект). Другими словами, он существует только до тех пор, пока на него ссылается хотя бы одна вложенная функция. #### Оптимизация на практике Но на практике движки JavaScript пытаются это оптимизировать. Они анализируют использование переменных и, если легко по коду понять, что внешняя переменная не используется – она удаляется. ***Одним из важных побочных эффектов в V8 (Chrome, Edge, Opera) является то, что такая переменная становится недоступной при отладке.***