113 lines
13 KiB
Markdown
113 lines
13 KiB
Markdown
---
|
||
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, в которых мы присваиваем функцию переменной.
|
||
|
||
#### Внутреннее и внешнее лексическое окружение
|
||
Когда запускается функция, в начале ее вызова автоматически создается новое лексическое окружение для хранения локальных переменных и параметров вызова.
|
||
|
||

|
||
|
||
В процессе вызова функции у нас есть два лексических окружения: внутреннее (для вызываемой функции) и внешнее (глобальное):
|
||
|
||
- Внутреннее лексическое окружение соответствует текущему выполнению 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.
|
||
|
||
Таким образом, мы имеем два вложенных лексических окружения, как в примере выше:
|
||

|
||
Отличие заключается в том, что во время выполнения makeCounter() создается крошечная вложенная функция, состоящая всего из одной строки: return count++. Мы ее еще не запускаем, а только создаем.
|
||
|
||
Все функции помнят лексическое окружение, в котором они были созданы. Технически здесь нет никакой магии: все функции имеют скрытое свойство [[Environment]], которое хранит ссылку на лексическое окружение, в котором была создана функция:
|
||

|
||
|
||
Таким образом, counter.[[Environment]] имеет ссылку на \{count: 0} лексического окружения. Так функция запоминает, где она была создана, независимо от того, где она вызывается. Ссылка на [[Environment]] устанавливается один раз и навсегда при создании функции.
|
||
|
||
Впоследствии, при вызове counter(), для этого вызова создается новое лексическое окружение, а его внешняя ссылка на лексическое окружение берется из counter.[[Environment]]:
|
||

|
||
Теперь, когда код внутри counter() ищет переменную count, он сначала ищет ее в собственном лексическом окружении (пустом, так как там нет локальных переменных), а затем в лексическом окружении внешнего вызова makeCounter(), где находит count и изменяет ее.
|
||
|
||
***Переменная обновляется в том лексическом окружении, в котором она существует.***
|
||

|
||
Если мы вызовем counter() несколько раз, то в одном и том же месте переменная count будет увеличена до 2, 3 и т.д.
|
||
|
||
💥 **Замыкания**
|
||
|
||
**Замыкание** – это функция, которая запоминает свои внешние переменные и может получить к ним доступ. В некоторых языках это невозможно, или функция должна быть написана специальным образом, чтобы получилось замыкание. Но, как было описано выше, в JavaScript, все функции изначально являются замыканиями.
|
||
|
||
То есть они автоматически запоминают, где были созданы, с помощью скрытого свойства `[[Environment]]`, и все они могут получить доступ к внешним переменным.
|
||
|
||
Когда на собеседовании фронтенд-разработчику задают вопрос: «что такое замыкание?», – правильным ответом будет определение замыкания и объяснения того факта, что все функции в JavaScript являются замыканиями, и, может быть, несколько слов о технических деталях: свойстве `[[Environment]]` и о том, как работает лексическое окружение.
|
||
|
||
Есть только одно исключение, когда функция создаётся с использованием `new Function`, в её `[[Environment]]` записывается ссылка не на внешнее лексическое окружение, в котором она была создана, а на глобальное. Поэтому такая функция имеет доступ только к глобальным переменным.
|
||
|
||
## Сборка мусора
|
||
Обычно лексическое окружение удаляется из памяти вместе со всеми переменными после завершения вызова функции. Это связано с тем, что на него нет ссылок. Как и любой объект JavaScript, оно хранится в памяти только до тех пор, пока к нему можно обратиться.
|
||
|
||
Однако если существует вложенная функция, которая все еще доступна после завершения функции, то она имеет свойство `[[Environment]]`, ссылающееся на лексическое окружение.
|
||
|
||
В этом случае лексическое окружение остается доступным даже после завершения работы функции.
|
||
|
||
Объект лексического окружения исчезает, когда становится недоступным (как и любой другой объект). Другими словами, он существует только до тех пор, пока на него ссылается хотя бы одна вложенная функция.
|
||
|
||
#### Оптимизация на практике
|
||
Но на практике движки JavaScript пытаются это оптимизировать. Они анализируют использование переменных и, если легко по коду понять, что внешняя переменная не используется – она удаляется.
|
||
|
||
***Одним из важных побочных эффектов в V8 (Chrome, Edge, Opera) является то, что такая переменная становится недоступной при отладке.*** |