update browser

This commit is contained in:
2025-03-06 16:09:21 +03:00
parent 1445c4f31f
commit 7484909864
16 changed files with 572 additions and 91 deletions

View File

@@ -3,111 +3,134 @@ sidebar_position: 6
---
# Область видимости переменных, замыкание
Источник: [https://learn.javascript.ru/closure](https://learn.javascript.ru/closure)
Источник: DeepSeek
Доп. источник: [https://learn.javascript.ru/closure](https://learn.javascript.ru/closure)
## Блоки кода
Если переменная объявлена внутри блока кода `{...}`, то она видна только внутри этого блока.
С помощью блоков `{...}` мы можем изолировать часть кода, выполняющую свою собственную задачу, с переменными, принадлежащими только ей
Для `if`, `for`, `while` и т.д. переменные, объявленные в блоке кода `{...}`, также видны только внутри
## Область видимости (scope)
В JavaScript **область видимости (scope)** определяет, где переменные, функции и другие идентификаторы могут быть использованы в коде. Понимание области видимости важно для написания корректного и предсказуемого кода. В JavaScript есть несколько типов областей видимости:
## Вложенные функции
Функция называется «вложенной», когда она создаётся внутри другой функции.
Она может получить доступ к внешним переменным.
Вложенная функция может быть возвращена: либо в качестве свойства нового объекта (если внешняя функция создаёт объект с методами), либо сама по себе. И затем может быть использована в любом месте. Не важно где, она всё так же будет иметь доступ к тем же внешним переменным.
1. **Глобальная область видимости (Global Scope)** \
Переменные, объявленные вне всех функций или блоков, находятся в глобальной области видимости. Они доступны из любого места в коде.
2. **Локальная область видимости (Function Scope)** \
Переменные, объявленные внутри функции, доступны только внутри этой функции. Это называется function scope.
3. **Блочная область видимости (Block Scope)** \
С появлением let и const в ES6 появилась блочная область видимости. Переменные, объявленные внутри блока (например, внутри {}), доступны только внутри этого блока. \
Обратите внимание, что var не имеет блочной области видимости
4. **Лексическая область видимости (Lexical Scope)** \
JavaScript использует лексическую (статическую) область видимости. Это означает, что область видимости определяется во время написания кода, а не во время выполнения. Вложенные функции имеют доступ к переменным из внешних функций.
5. **Область видимости модулей (Module Scope)**
В ES6 появились модули, которые имеют свою собственную область видимости. Переменные, объявленные в модуле, не доступны извне, если они не экспортированы.
6. **Цепочка областей видимости (Scope Chain)**
Когда JavaScript ищет переменную, он сначала проверяет текущую область видимости, затем переходит к внешним областям, пока не достигнет глобальной области. Если переменная не найдена, возникает ошибка.
7. **Замыкания (Closures)**
Замыкание — это функция, которая запоминает свое лексическое окружение даже после того, как внешняя функция завершила выполнение. Это возможно благодаря области видимости.
## Лексическое окружение
- **Глобальная область видимости:** переменные доступны везде.
- **Локальная (function scope):** переменные доступны только внутри функции.
- **Блочная (block scope):** переменные доступны только внутри блока ({}), если объявлены через let или const.
- **Лексическая область видимости:** вложенные функции имеют доступ к переменным из внешних функций.
- **Модульная область видимости:** переменные в модулях изолированы.
#### Переменные
В JavaScript у каждой выполняемой функции, блока кода `{...}` и скрипта есть связанный с ними внутренний (скрытый) объект, называемый ***лексическим окружением** `LexicalEnvironment`.
Объект лексического окружения состоит из двух частей:
1. **Environment Record** объект, в котором как свойства хранятся все локальные переменные (а также некоторая другая информация, такая как значение this).
2. Ссылка на **внешнее лексическое окружение** то есть то, которое соответствует коду снаружи (снаружи от текущих фигурных скобок).
## Замыкание (closure)
**Замыкание (closure)** в JavaScript — это мощный механизм, который позволяет функциям "запоминать" свое лексическое окружение (область видимости), даже после того, как внешняя функция завершила выполнение. Замыкания часто используются для создания приватных переменных, реализации функций высшего порядка и других паттернов программирования.
**«Переменная» это просто свойство специального внутреннего объекта: `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;
function outer() {
let outerVar = "Я из внешней функции";
return function() {
return count++;
};
function inner() {
console.log(outerVar); // Используем переменную из внешней функции
}
return inner;
}
let counter = makeCounter();
const closureFunc = outer(); // outer завершила выполнение
closureFunc(); // "Я из внешней функции" — inner помнит outerVar
```
В начале каждого вызова makeCounter() создается новый объект лексического окружения, в котором хранятся переменные для конкретного запуска makeCounter.
Здесь:
1. Функция `outer` создает переменную `outerVar`.
2. Функция `inner` использует эту переменную.
3. Когда outer завершает выполнение, она возвращает `inner`.
4. Переменная `closureFunc` теперь содержит функцию `inner`, которая сохраняет доступ к `outerVar`.
Таким образом, мы имеем два вложенных лексических окружения, как в примере выше:
![](images/06-closure-2.png)
Отличие заключается в том, что во время выполнения makeCounter() создается крошечная вложенная функция, состоящая всего из одной строки: return count++. Мы ее еще не запускаем, а только создаем.
### Почему это работает?
JavaScript использует **лексическую область видимости (lexical scoping)**. Это означает, что область видимости функции определяется в момент ее создания, а не в момент вызова. Внутренняя функция (`inner`) "замыкается" на переменные из внешней функции (`outer`), даже если внешняя функция уже завершила выполнение.
Все функции помнят лексическое окружение, в котором они были созданы. Технически здесь нет никакой магии: все функции имеют скрытое свойство [[Environment]], которое хранит ссылку на лексическое окружение, в котором была создана функция:
![](images/06-closure-3.png)
### Практическое применение замыканий
1. **Приватные переменные** \
Замыкания позволяют создавать приватные переменные, которые недоступны извне.
```js
function createCounter() {
let count = 0; // Приватная переменная
Таким образом, counter.[[Environment]] имеет ссылку на \{count: 0} лексического окружения. Так функция запоминает, где она была создана, независимо от того, где она вызывается. Ссылка на [[Environment]] устанавливается один раз и навсегда при создании функции.
return function() {
count++;
return count;
};
}
Впоследствии, при вызове counter(), для этого вызова создается новое лексическое окружение, а его внешняя ссылка на лексическое окружение берется из counter.[[Environment]]:
![](images/06-closure-4.png)
Теперь, когда код внутри counter() ищет переменную count, он сначала ищет ее в собственном лексическом окружении (пустом, так как там нет локальных переменных), а затем в лексическом окружении внешнего вызова makeCounter(), где находит count и изменяет ее.
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
```
Здесь переменная `count` недоступна извне, но функция-счетчик может изменять и возвращать ее значение.
***Переменная обновляется в том лексическом окружении, в котором она существует.***
![](images/06-closure-5.png)
Если мы вызовем counter() несколько раз, то в одном и том же месте переменная count будет увеличена до 2, 3 и т.д.
2. **Функции высшего порядка** \
Замыкания часто используются в функциях высшего порядка, таких как `map`, `filter`, `reduce`.
```js
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
💥 **Замыкания**
const double = createMultiplier(2);
console.log(double(5)); // 10
```
Здесь `createMultiplier` возвращает функцию, которая "запоминает" значение `multiplier`.
**Замыкание** это функция, которая запоминает свои внешние переменные и может получить к ним доступ. В некоторых языках это невозможно, или функция должна быть написана специальным образом, чтобы получилось замыкание. Но, как было описано выше, в JavaScript, все функции изначально являются замыканиями.
3. **Модули и инкапсуляция** \
Замыкания позволяют создавать модули с приватными методами и переменными.
```js
const module = (function() {
let privateVar = "Я приватная";
То есть они автоматически запоминают, где были созданы, с помощью скрытого свойства `[[Environment]]`, и все они могут получить доступ к внешним переменным.
function privateMethod() {
console.log(privateVar);
}
Когда на собеседовании фронтенд-разработчику задают вопрос: «что такое замыкание?», правильным ответом будет определение замыкания и объяснения того факта, что все функции в JavaScript являются замыканиями, и, может быть, несколько слов о технических деталях: свойстве `[[Environment]]` и о том, как работает лексическое окружение.
return {
publicMethod: function() {
privateMethod();
}
};
})();
Есть только одно исключение, когда функция создаётся с использованием `new Function`, в её `[[Environment]]` записывается ссылка не на внешнее лексическое окружение, в котором она была создана, а на глобальное. Поэтому такая функция имеет доступ только к глобальным переменным.
module.publicMethod(); // "Я приватная"
console.log(module.privateVar); // undefined (недоступно)
```
## Сборка мусора
Обычно лексическое окружение удаляется из памяти вместе со всеми переменными после завершения вызова функции. Это связано с тем, что на него нет ссылок. Как и любой объект JavaScript, оно хранится в памяти только до тех пор, пока к нему можно обратиться.
4. **Колбэки и асинхронный код** \
Замыкания часто используются в асинхронном коде, чтобы сохранить состояние.
Здесь функция внутри setTimeout "запоминает" значение name.
```js
function delayedGreeting(name) {
setTimeout(function() {
console.log(`Привет, ${name}!`);
}, 1000);
}
delayedGreeting("Алексей"); // Через 1 секунду: "Привет, Алексей!"
```
Здесь функция внутри setTimeout "запоминает" значение name.
Однако если существует вложенная функция, которая все еще доступна после завершения функции, то она имеет свойство `[[Environment]]`, ссылающееся на лексическое окружение.
В этом случае лексическое окружение остается доступным даже после завершения работы функции.
Объект лексического окружения исчезает, когда становится недоступным (как и любой другой объект). Другими словами, он существует только до тех пор, пока на него ссылается хотя бы одна вложенная функция.
#### Оптимизация на практике
Но на практике движки JavaScript пытаются это оптимизировать. Они анализируют использование переменных и, если легко по коду понять, что внешняя переменная не используется она удаляется.
***Одним из важных побочных эффектов в V8 (Chrome, Edge, Opera) является то, что такая переменная становится недоступной при отладке.***
### Как избежать утечек памяти?
Замыкания могут приводить к утечкам памяти, если они сохраняют ссылки на большие объекты, которые больше не используются. Чтобы избежать этого:
- Убедитесь, что замыкания не сохраняют ненужные данные.
- Используйте null для очистки ссылок, когда они больше не нужны.