update browser
This commit is contained in:
@@ -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, в которых мы присваиваем функцию переменной.
|
||||
|
||||
#### Внутреннее и внешнее лексическое окружение
|
||||
Когда запускается функция, в начале ее вызова автоматически создается новое лексическое окружение для хранения локальных переменных и параметров вызова.
|
||||
|
||||

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

|
||||
Отличие заключается в том, что во время выполнения makeCounter() создается крошечная вложенная функция, состоящая всего из одной строки: return count++. Мы ее еще не запускаем, а только создаем.
|
||||
### Почему это работает?
|
||||
JavaScript использует **лексическую область видимости (lexical scoping)**. Это означает, что область видимости функции определяется в момент ее создания, а не в момент вызова. Внутренняя функция (`inner`) "замыкается" на переменные из внешней функции (`outer`), даже если внешняя функция уже завершила выполнение.
|
||||
|
||||
Все функции помнят лексическое окружение, в котором они были созданы. Технически здесь нет никакой магии: все функции имеют скрытое свойство [[Environment]], которое хранит ссылку на лексическое окружение, в котором была создана функция:
|
||||

|
||||
### Практическое применение замыканий
|
||||
1. **Приватные переменные** \
|
||||
Замыкания позволяют создавать приватные переменные, которые недоступны извне.
|
||||
```js
|
||||
function createCounter() {
|
||||
let count = 0; // Приватная переменная
|
||||
|
||||
Таким образом, counter.[[Environment]] имеет ссылку на \{count: 0} лексического окружения. Так функция запоминает, где она была создана, независимо от того, где она вызывается. Ссылка на [[Environment]] устанавливается один раз и навсегда при создании функции.
|
||||
return function() {
|
||||
count++;
|
||||
return count;
|
||||
};
|
||||
}
|
||||
|
||||
Впоследствии, при вызове counter(), для этого вызова создается новое лексическое окружение, а его внешняя ссылка на лексическое окружение берется из counter.[[Environment]]:
|
||||

|
||||
Теперь, когда код внутри counter() ищет переменную count, он сначала ищет ее в собственном лексическом окружении (пустом, так как там нет локальных переменных), а затем в лексическом окружении внешнего вызова makeCounter(), где находит count и изменяет ее.
|
||||
const counter = createCounter();
|
||||
console.log(counter()); // 1
|
||||
console.log(counter()); // 2
|
||||
console.log(counter()); // 3
|
||||
```
|
||||
Здесь переменная `count` недоступна извне, но функция-счетчик может изменять и возвращать ее значение.
|
||||
|
||||
***Переменная обновляется в том лексическом окружении, в котором она существует.***
|
||||

|
||||
Если мы вызовем 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 для очистки ссылок, когда они больше не нужны.
|
||||
|
||||
68
docs/javascript/08-promises/07-event-loop.md
Normal file
68
docs/javascript/08-promises/07-event-loop.md
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
# Event loop
|
||||
Источник: DeepSeek \
|
||||
Видеоразбор: [JavaScript - Event Loop | Асинхронность, Web API, Очереди (макро/микро)задач, Отрисовка кадров](https://www.youtube.com/watch?v=YTLXrji4DJc)
|
||||
|
||||
Event Loop (цикл событий) — это механизм, который позволяет JavaScript выполнять асинхронные операции, несмотря на то, что сам язык является однопоточным. Он отвечает за обработку событий, вызовов колбэков и выполнение задач в правильном порядке.
|
||||
|
||||
## Основные компоненты Event Loop
|
||||
1. **Call Stack (стек вызовов):**
|
||||
- Это структура данных, которая отслеживает текущие выполняемые функции.
|
||||
- Когда функция вызывается, она добавляется в стек. Когда функция завершается, она удаляется из стека.
|
||||
- JavaScript однопоточный, поэтому в один момент времени может выполняться только одна задача.
|
||||
2. **Web APIs (или Browser APIs):**
|
||||
- Это API, предоставляемые браузером (или средой выполнения, например, Node.js), которые позволяют выполнять асинхронные операции, такие как `setTimeout`, `fetch`, `XMLHttpRequest`, обработка событий (клики, таймеры и т.д.).
|
||||
- Когда асинхронная операция запускается, она передается в Web API, и основной поток JavaScript продолжает выполнение.
|
||||
3. **Callback Queue (очередь колбэков):**
|
||||
- Когда асинхронная операция завершается, ее колбэк помещается в очередь колбэков.
|
||||
- Примеры: колбэки из `setTimeout`, обработчики событий.
|
||||
4. **Microtask Queue (очередь микрозадач):**
|
||||
- Это отдельная очередь для микрозадач, таких как промисы (`Promise`) и `MutationObserver`.
|
||||
- Микрозадачи имеют приоритет над задачами из Callback Queue.
|
||||
5. **Event Loop:**
|
||||
- Это бесконечный цикл, который проверяет, пуст ли Call Stack.
|
||||
- Если Call Stack пуст, Event Loop берет задачи из Callback Queue и Microtask Queue и помещает их в Call Stack для выполнения.
|
||||
|
||||
## Как работает Event Loop
|
||||
1. Синхронный код:
|
||||
- Весь синхронный код выполняется сразу, попадая в Call Stack.
|
||||
2. Асинхронный код:
|
||||
- Когда встречается асинхронная операция (например, `setTimeout` или `fetch`), она передается в Web API, и основной поток продолжает выполнение.
|
||||
- После завершения асинхронной операции ее колбэк помещается в Callback Queue (или Microtask Queue, если это промис).
|
||||
3. Обработка очередей:
|
||||
- Когда Call Stack пуст, Event Loop сначала проверяет Microtask Queue.
|
||||
- Все микрозадачи (например, колбэки промисов) выполняются до того, как Event Loop перейдет к Callback Queue.
|
||||
- После выполнения всех микрозадач Event Loop берет задачи из Callback Queue и помещает их в Call Stack.
|
||||
|
||||
## Пример работы Event Loop
|
||||
```js
|
||||
console.log("Start");
|
||||
|
||||
setTimeout(() => {
|
||||
console.log("Timeout");
|
||||
}, 0);
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
console.log("Promise");
|
||||
});
|
||||
|
||||
console.log("End");
|
||||
```
|
||||
#### Вывод:
|
||||
```
|
||||
Start
|
||||
End
|
||||
Promise
|
||||
Timeout
|
||||
```
|
||||
|
||||
#### Объяснение:
|
||||
1. Сначала выполняется синхронный код: `console.log("Start")` и `console.log("End")`.
|
||||
2. `setTimeout` передается в Web API, а его колбэк попадает в Callback Queue.
|
||||
3. Промис сразу резолвится, и его колбэк попадает в Microtask Queue.
|
||||
4. После завершения синхронного кода Event Loop сначала выполняет микрозадачи (колбэк промиса), а затем переходит к Callback Queue (колбэк `setTimeout`).
|
||||
|
||||
Event Loop позволяет JavaScript эффективно обрабатывать асинхронные операции, несмотря на однопоточность. Он управляет порядком выполнения задач, используя Call Stack, Web APIs, Callback Queue и Microtask Queue. Понимание этого механизма важно для написания эффективного и предсказуемого асинхронного кода.
|
||||
Reference in New Issue
Block a user