update func

This commit is contained in:
2025-03-05 12:33:59 +03:00
parent e60ec041ae
commit 31caa0e6de
15 changed files with 539 additions and 1 deletions

View File

@@ -0,0 +1,113 @@
---
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) является то, что такая переменная становится недоступной при отладке.***