текст перекладу
Що таке Execution Context в JavaScript? Це не страшно, ми поступово розберемо всі деталі цього поняття, тому читайте далі і сподіваюся, що всі ваші сумніви зникнуть. Почнемо!
function sayName() {
var name = "kawsarkabir";
console.log("The Name is ", + name);
}
Тут ви бачите просту функцію на JavaScript. Те, що вона робить — не так важливо, головне — це як JavaScript бачить цей код?
JavaScript спочатку намагається розібрати цей код на частини за допомогою свого процесу. Якщо подивитися на зображення вище, все стане зрозуміліше, я поділив код на частини. Перша лінія — це ключове слово функції, потім є ім’я функції, функція визначена. На другому рядку JavaScript бачить, що є змінна і її значення. Цей процес, коли JavaScript розділяє код на частини, називається токенізацією.
Після токенізації наступний крок — JavaScript використовує ці маленькі частини для створення дерева, яке називається AST — (Abstract Syntax Tree).
Abstract Syntax Tree (AST) — це те, що ми отримуємо після того, як JavaScript розбиває код на токени, ці токени формують структуру дерева, і з цієї структури дерево JavaScript, як вхід, генерує остаточний код, який є машинним кодом, що може успішно виконуватись у браузері.
Якщо ви хочете дізнатися більше про Abstract Syntax Tree, ви можете перейти за цим посиланням. І ось як виглядає наша простенька функція після того, як JavaScript розбив її на частини:
Що таке Execution Context?
До цього часу ми розбирали, як JavaScript сприймає код, як він розуміє, що потрібно зробити, щоб виконати його. Тепер давайте зрозуміємо, що таке Execution Context?
function sayName() {
var name = "kawsarkabir";
console.log("The Name is ", + name);
}
текст перекладу
-->
Коли ви пишете код у вихідному файлі, ваш код знаходиться в певному контексті. Зверху є частина коду, знизу теж є частина коду. Тепер уявіть, що між верхнім та нижнім кодом немає жодного зв'язку, або ж цей зв'язок може бути.
Коли ми розміщуємо код у файлі лексично, лексичний контекст означає розміщення чогось у певному місці. Ви, можливо, чули про лексичний контекст в JavaScript. Це означає, що функція на верхній лінії знаходиться на позиціях з 7 по 10.
Тепер, коли JavaScript виконує код лінія за лінією, на сьомій лінії він розуміє, що тут є функція, але до того, як вона буде викликана чи виконана, JavaScript просто знатиме, що лексична позиція цієї функції така-то. І коли ми викликаємо або виконуємо її, саме тоді JavaScript створює спеціальну структуру, яка називається Execution Context. Тепер, якщо хтось запитає, що таке Execution Context, ви можете сказати так:
Execution Context — це середовище, в якому виконується код JavaScript. Кожного разу, коли код виконується в JavaScript, він працює в рамках Execution Context.
- Глобальний Execution Context
- Функціональний Execution Context
Що таке глобальний Execution Context?
Коли код JavaScript виконується вперше, JavaScript автоматично створює Execution Context, який називається глобальним Execution Context (GEC). Глобальний контекст — це те, що знаходиться поза функцією.
Уявіть, що ви створюєте HTML і JavaScript файл.
В JavaScript нічого немає, жодного рядка коду, але навіть тоді за замовчуванням створюється Execution Context. І тут є дві важливі речі:
- Створюється глобальний об'єкт, який є об'єктом window (для браузера) або global (для Node).
- Значення this встановлюється як глобальний об'єкт, тобто this і window — це одне й те саме. У програмі може бути лише один глобальний Execution Context.
Тепер, коли ми вже розібрали, що таке Execution Context, давайте розглянемо дві фази, які є в кожному Execution Context: 1. фаза створення (creation phase) і 2. фаза виконання (execution phase).
1. Фаза створення (creation phase):
У глобальному Execution Context, як ми бачили раніше, відбуваються дві основні дії. Один — це створення об'єкта window (глобальний об'єкт), інший — це значення this, яке вказує на глобальний об'єкт.
Крім цього, виконуються й інші дії, наприклад, є змінні чи функції, які оголошені, але ще не виконані. Тому JavaScript виділяє місце в пам'яті для змінних і функцій, і встановлює змінним значення undefined, а функціям — відповідні місця в пам'яті згідно з їх лексичним положенням. І тільки після цього код потрапляє в фазу виконання.
Зверніть увагу на зображення нижче, де я детально пояснив.
Тепер давайте подивимося, що відбувається в фазі виконання цього коду:
- Спочатку він побачить, що є змінна name, і встановить її значення на
kawsarkabir
, яке раніше було undefined в фазі створення.
Далі він перевірить, чи є ще щось, що можна виконати, і знайде функцію sayName
. Він побачить, що функція оголошена, але ще не викликана. Це означає, що в контексті глобального Execution Context більше нічого виконувати не потрібно.
Якщо підсумувати, що відбувається за лаштунками в глобальному Execution Context, то створюються об'єкти window, this, виділяється пам'ять для змінної name, а функція sayName
отримує місце в пам'яті. Тепер ми можемо перейти до функції і подивитися, що відбудеться в її Execution Context.
Що таке функціональний Execution Context?
текст перекладу
Кожного разу, коли викликається функція, JavaScript-двигун створює новий Execution Context для цієї функції. Кожна функція має свій власний Execution Context. Може бути кілька Execution Context для різних функцій. Execution Context функцій має доступ до всього коду глобального Execution Context, але глобальний Execution Context не має доступу до коду функціональних Execution Context. Коли JavaScript-двигун виконує код у глобальному контексті, якщо він зустрічає виклик функції, він створює новий Execution Context для цієї функції. У контексті браузера, якщо код виконується в строгому режимі, то значення this буде undefined, інакше це буде об'єкт window для функціонального Execution Context.
Як і у глобальному Execution Context, функціональний Execution Context має 2 фази: фаза створення (creation phase) і фаза виконання (execution phase).
Давайте розглянемо приклад, як це працює:
var name = "kawsar kabir";
function sayName() {
console.log(this.name);
}
sayName();
У цьому випадку ми маємо функцію sayName()
, яку викликаємо. Зараз ми вже знаємо, що таке глобальний Execution Context — це все, що знаходиться поза функцією, і ці дві лінії коду знаходяться поза функцією.
var name = "kawsar kabir";
sayName();
Отже, ці дві лінії спробують виконатися в глобальному Execution Context.
Потім у функціональному Execution Context буде перевірено, чи є ще якісь змінні, які треба створити. Якщо такі є, то для них буде виділено пам'ять, а значення змінних буде встановлено як undefined. Якщо всередині однієї функції є інша функція, то для неї також буде створено новий Execution Context. Отже, в фазі створення створюються змінні та функції, виділяється пам'ять. А в фазі виконання ці змінні та функції виконуються.
Тепер подивимося, що відбувається в фазі створення для цього коду:
Спочатку буде створено глобальний Execution Context, в його фазі створення значення name буде undefined. Також для name буде виділено пам'ять, а функція sayName()
отримає своє місце в пам'яті.
Фаза виконання:
Тут значення name буде призначено як kawsarkabir
. Тепер у фазі виконання глобального Execution Context він побачить, що викликалася функція. Тому для функції sayName()
буде створено новий функціональний Execution Context.
Тепер для функції будуть дві фази: фаза створення та фаза виконання, як зазвичай. Далі JavaScript перевірить, чи є інші функції, такі як log()
, для яких також створюється новий контекст. Отже, це виглядає як дерево, і JavaScript має якось тримати це під контролем.
Що таке Call Stack?
console.log("inside global execution context");
var x = 10;
function exampleFunc1() {
console.log("inside exampleFunc1 execution context");
var y = 20;
var user = {
name: "kawsarkabir",
country: "Bangladesh",
};
function exampleFunc2() {
console.log("inside exampleFunc2() execution context");
console.log("Exiting exampleFunc2 execution context");
}
exampleFunc2();
console.log("exiting exampleFunc1 execution context");
}
exampleFunc1();
console.log("exiting global execution context");
Щоб зрозуміти, що таке Call Stack, давайте подивимося на цей приклад. Спочатку у нас є функція. Тимчасово забудемо про console.log
. Подивимося, що є в глобальному Execution Context?
Тут є var x = 10;
, exampleFunc1();
, тепер подивимося, що є у функціональному Execution Context? Подивіться на зображення нижче.
текст перекладу
Тут спочатку є змінна, потім об'єкт, а потім ще одна функція, і ця функція знову викликається. Це означає, що для неї буде створено новий функціональний Execution Context, і там буде перевірено, чи є ще якісь змінні або інші елементи. Добре, давайте спробуємо побудувати дерево, як ми це робили раніше.
Сподіваюся, ви розумієте, що відбувається на зображенні, оскільки ми вже робили це раніше.
Тому не будемо зупинятися на деталях. Тут показано, як працюють Global Execution Context, Function Execution Context та як Call Stack управляє ними.
Тепер давайте заглибимося ще більше. Ми поговоримо про управління пам'яттю в JavaScript — спробуємо зрозуміти, як працюють Call Stack та Heap Memory.
1. Що таке Call Stack?
Call Stack — це тип структури даних (LIFO — Last In, First Out), де відслідковуються Execution Context.
Як це працює?
-
Створення Execution Context: Кожного разу, коли викликається функція, JavaScript створює новий Execution Context для цієї функції і додає його в Call Stack.
-
Завершення виконання: Коли виконання функції завершується, цей Execution Context вилучається з Call Stack.
Flow Call Stack в цьому прикладі:
Спочатку створюється Global Execution Context (GEC) і додається в Call Stack. Коли викликається exampleFunc1(), для неї створюється новий Function Execution Context (FEC) і додається в Stack.
Після цього, коли викликається exampleFunc2(), для неї також створюється новий FEC і додається в Stack. Після завершення виконання функцій, вони по черзі вилучаються з Call Stack.
2.
Що таке Heap Memory:
Heap (кучова пам'ять) - це простір пам'яті, де JavaScript об'єкти та функції зберігаються. Це неструктурований простір пам'яті, який використовується для зберігання великих і динамічних даних.
Приклад використання Heap Memory:
Коли ми оголошуємо var user = { name: “kawsarkabir”, country: “Bangladesh” }, об'єкт зберігається в Heap Memory. В Call Stack (стек викликів) зберігається лише посилання на цей об'єкт, тобто його адреса в пам'яті Heap.
3. Управління кодом та потік пам'яті:
Примітивні значення: Примітивні типи даних (як-от number, string, boolean) зберігаються безпосередньо в Call Stack.
Приклад: var x = 10; → x зберігається в Call Stack як 10.
Reference values (Посилальні значення): Об'єкти або функції, як посилальні типи даних, зберігаються в Heap.
Приклад: об'єкт user зберігається в Heap, а в Call Stack зберігається його посилання (вказівник).
4. Координація між Call Stack та Heap:
Коли відбувається виклик функції, в Call Stack створюється контекст. Якщо в цьому контексті є об'єкт або посилання, він отримує дані з Heap. Коли виконання завершується, контекст видаляється з Call Stack, але дані з Heap не видаляються до того часу, поки не відбудеться процес збирання сміття (Garbage Collection).
5. Збирання сміття (Garbage Collection):
JavaScript автоматично управляє пам'яттю за допомогою збирача сміття. Коли на об'єкт більше не посилається жоден інший елемент, збирач сміття видаляє цей об'єкт з Heap.
Якщо ви хочете отримати більше інформації, ось кілька відео ресурсів: відео1 відео2
Сподіваюся, з цього моменту, якщо хтось запитає вас про це або ви зіткнетеся з подібним питанням на співбесіді, ви зможете впоратися на відмінно.
Щасливого кодування!
Перекладено з: Understanding JavaScript Execution Context: Call Stack, Heap Memory, and Visualizing Code Execution