Пошук уроків, статей та іншого контенту
Дізнайтеся, що таке контекст виконання та як JavaScript створює глобальний контекст і нові контексти під час виклику функцій. Розберіть життєвий цикл контексту та його роль у виконанні програми.
Execution context — це внутрішня структура, яку JavaScript створює для виконання певного фрагмента коду.
Вона містить інформацію, необхідну рушію JavaScript, зокрема:
доступні змінні та функції;
параметри поточної функції;
значення this;
посилання на зовнішнє лексичне оточення;
інші внутрішні дані, потрібні для виконання коду.
Execution context — не те саме, що область видимості, хоча ці поняття тісно пов’язані. Область видимості описує, які імена доступні з певного місця коду, а execution context описує поточний стан виконання цього коду.
JavaScript створює execution context:
один раз для глобального коду;
щоразу під час виклику звичайної функції;
під час виконання коду через eval().
У більшості програм розробник безпосередньо працює з глобальним і функціональними контекстами.
Коли JavaScript починає виконувати файл або скрипт, він спочатку створює Global Execution Context.
У ньому виконуються інструкції верхнього рівня:
const applicationName = "Dashboard";
console.log(applicationName);Цей контекст існує протягом усього часу роботи програми.
Глобальний контекст:
створюється до виконання першого рядка програми;
є початковим контекстом у стеку викликів;
містить глобальні оголошення;
не видаляється, доки працює відповідне середовище виконання.
У середовищі JavaScript існує глобальний об’єкт. У сучасному коді для доступу до нього використовують globalThis.
У браузері глобальним об’єктом є window, у Node.js — global. globalThis дозволяє звертатися до глобального об’єкта незалежно від середовища:
console.log(globalThis);Однак глобальна змінна і властивість глобального об’єкта — не завжди одне й те саме.
У звичайному браузерному скрипті оголошення через var зазвичай створює властивість глобального об’єкта:
var browserValue = 10;
console.log(browserValue);
console.log(globalThis.browserValue);Натомість let і const створюють глобальні прив’язки, але не додають їх як властивості globalThis:
let modernValue = 20;
const anotherValue = 30;
console.log(modernValue);
console.log(globalThis.modernValue); // undefinedПоведінка верхньорівневих змінних також залежить від типу модуля та середовища. Наприклад, у ES-модулях верхньорівневий this має значення undefined.
Тому не варто використовувати глобальний об’єкт як основний спосіб зберігання стану програми.
Створення execution context умовно можна поділити на дві фази:
фаза створення;
фаза виконання.
Це не два окремі JavaScript-виклики, а модель, яка допомагає пояснити поведінку рушія.
Під час фази створення JavaScript готує контекст до виконання:
створює лексичне оточення;
реєструє оголошення змінних і функцій;
визначає зовнішнє оточення;
встановлює значення this;
готує інші внутрішні структури контексту.
Саме цією фазою пояснюються деякі випадки hoisting — підняття оголошень.
Під час фази виконання JavaScript:
виконує інструкції по черзі;
обчислює вирази;
присвоює значення змінним;
викликає функції;
змінює стан об’єктів і програмних структур.
Розглянемо приклад:
console.log(typeof declaredFunction); // "function"
console.log(typeof declaredVariable); // "undefined"
function declaredFunction() {
return "Готово";
}
var declaredVariable = 42;
console.log(declaredFunction()); // "Готово"
console.log(declaredVariable); // 42Під час створення контексту оголошення функції вже повністю доступне, а var declaredVariable створюється зі значенням undefined.
Це не означає, що весь код переміщується вгору. JavaScript лише заздалегідь створює відповідні прив’язки.
let, const і temporal dead zoneЗмінні, оголошені через let і const, також реєструються під час створення контексту, але до моменту виконання їхнього оголошення вони перебувають у temporal dead zone, або TDZ.
console.log(typeof value); // ReferenceError
let value = 100;Звернення до value до рядка з її оголошенням викликає ReferenceError.
Важливо відрізняти це від поведінки var:
console.log(varValue); // undefined
var varValue = 100;У випадку var прив’язка вже існує і має значення undefined. У випадку let або const прив’язка існує, але доступ до неї до ініціалізації заборонений.
Коли JavaScript викликає функцію, він створює новий Function Execution Context.
function greet(name) {
const message = `Привіт, ${name}!`;
return message;
}
const result = greet("Олено");
console.log(result);Під час виклику greet("Олено") створюється окремий контекст, який містить:
параметр name;
локальну змінну message;
значення this;
посилання на зовнішнє лексичне оточення;
службову інформацію про виконання функції.
Після завершення функції її execution context видаляється зі стеку викликів. Локальні змінні зазвичай стають недоступними, якщо на них більше немає посилань.
Одна й та сама функція може викликатися багато разів. Для кожного виклику створюється новий контекст:
function add(a, b) {
const result = a + b;
return result;
}
console.log(add(2, 3));
console.log(add(10, 20));Контекст для add(2, 3) має:
a = 2
b = 3
result = 5Контекст для add(10, 20) має інший стан:
a = 10
b = 20
result = 30Локальні змінні одного виклику не змішуються з локальними змінними іншого виклику.
Кожен контекст має доступ до власного оточення і до зовнішніх оточень.
const globalPrefix = "Результат";
function multiply(value, multiplier) {
const product = value * multiplier;
return `${globalPrefix}: ${product}`;
}
console.log(multiply(5, 3));Під час виконання multiply пошук імен відбувається приблизно так:
JavaScript шукає value у локальному контексті функції;
шукає multiplier там само;
шукає product там само;
шукає globalPrefix у зовнішньому глобальному оточенні.
Цей ланцюжок називають scope chain, або ланцюжком областей видимості.
Якщо ім’я не знайдено ні в локальному, ні в зовнішніх оточеннях, виникає ReferenceError:
function showValue() {
console.log(notDefined);
}
showValue(); // ReferenceErrorExecution contexts організовані у call stack, або стеку викликів.
Стек працює за принципом LIFO:
останній доданий контекст видаляється першим.
Приклад:
function first() {
second();
}
function second() {
third();
}
function third() {
console.log("Виконання третьої функції");
}
first();Послідовність роботи стеку:
створюється глобальний контекст;
викликається first — додається контекст first;
усередині first викликається second;
усередині second викликається third;
third завершується і видаляється;
завершується second і видаляється;
завершується first і видаляється;
залишається глобальний контекст.
У спрощеному вигляді стек виглядає так:
Global
Global → first
Global → first → second
Global → first → second → third
Global → first → second
Global → first
GlobalЯкщо функції викликають одна одну без умови завершення, стек переповнюється:
function repeat() {
repeat();
}
repeat(); // RangeError: Maximum call stack size exceededДля виклику функції можна виділити такий життєвий цикл:
JavaScript зустрічає виклик функції.
Створюється новий Function Execution Context.
У ньому створюються параметри, локальні прив’язки та this.
Контекст додається на вершину call stack.
Виконується тіло функції.
Функція повертає значення або завершується без явного return.
Контекст видаляється зі стеку.
Наприклад:
function calculateTotal(price, quantity) {
const total = price * quantity;
return total;
}
const orderTotal = calculateTotal(25, 4);
console.log(orderTotal);Під час виклику:
Global
Global → calculateTotal
GlobalЗначення total належить контексту calculateTotal, а orderTotal — глобальному контексту.
Завершення функції не завжди означає, що її локальні дані одразу зникають.
Якщо внутрішня функція продовжує посилатися на змінні зовнішньої функції, ці змінні зберігаються завдяки замиканню.
function createCounter() {
let count = 0;
return function increment() {
count += 1;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3Що відбувається:
викликається createCounter;
створюється локальна змінна count;
функція increment повертається назовні;
контекст createCounter завершується;
increment зберігає доступ до count;
кожен виклик counter() змінює те саме замкнене значення.
Отже, execution context може бути видалений зі стеку, але його лексичне оточення може залишитися доступним, якщо на нього посилається замикання.
this у контекстіЗначення this є частиною контексту виконання, але воно визначається не просто місцем оголошення функції. Для звичайної функції значення this залежить від способу виклику.
"use strict";
function showThis() {
console.log(this);
}
showThis(); // undefinedУ strict mode звичайний виклик функції не підставляє глобальний об’єкт як this.
Якщо функцію викликати як метод об’єкта, this вказує на об’єкт перед крапкою:
"use strict";
const user = {
name: "Марія",
introduce() {
console.log(`Мене звати ${this.name}`);
}
};
user.introduce(); // Мене звати МаріяФункції-стрілки не створюють власного this. Вони використовують this із зовнішнього контексту:
const user = {
name: "Марія",
createMessage() {
const getMessage = () => `Ім'я: ${this.name}`;
return getMessage();
}
};
console.log(user.createMessage());Тому звичайні та стрілкові функції можуть мати різну поведінку навіть за однакового тіла.
JavaScript має різні правила для звичайних скриптів і ES-модулів.
У браузері звичайний скрипт завантажується без type="module":
<script src="app.js"></script>Для такого коду діють правила глобального скриптового контексту.
Модуль підключається так:
<script type="module" src="app.js"></script>Модулі мають власне модульне лексичне оточення:
верхньорівневі змінні модуля не стають глобальними;
модуль виконується в strict mode;
верхньорівневий this дорівнює undefined;
код модуля має власні імпорти та експорти.
У Node.js також важливо розрізняти ES-модулі та CommonJS-модулі. Їхня обгортка, значення верхньорівневого this і доступ до змінних можуть відрізнятися.
const applicationName = "Shop";
function createOrder(price, quantity) {
const total = price * quantity;
function formatOrder() {
return `${applicationName}: ${quantity} × ${price} = ${total}`;
}
return formatOrder();
}
console.log(createOrder(150, 2));Послідовність виконання:
створюється глобальний контекст;
у глобальному оточенні створюються applicationName і createOrder;
виконується виклик createOrder(150, 2);
створюється контекст createOrder;
у ньому створюються price, quantity і total;
створюється вкладена функція formatOrder;
викликається formatOrder;
її контекст шукає applicationName у глобальному оточенні;
formatOrder повертає рядок;
завершується createOrder;
глобальний контекст продовжує виконання наступних інструкцій.
У цьому прикладі вкладена функція має доступ до:
власних змінних;
змінних createOrder;
глобальної змінної applicationName.
Це приклад лексичного ланцюжка та замикання.
thisthis є лише однією з частин execution context. Контекст також пов’язаний із параметрами, локальними змінними, зовнішнім оточенням і станом виконання.
let і const не існують до рядка оголошенняВони створюються під час підготовки контексту, але перебувають у TDZ до ініціалізації. Тому звернення до них раніше викликає помилку.
Кожен виклик функції отримує новий execution context. Навіть якщо викликається та сама функція, її параметри та локальні змінні належать конкретному виклику.
Call stack зберігає поточні синхронні execution contexts. Event loop і черги завдань керують асинхронними операціями, але це окрема частина моделі виконання JavaScript.
Якщо замикання або інше посилання все ще використовує локальні дані, відповідне лексичне оточення може залишатися в пам’яті.
Глобальні дані доступні з багатьох місць програми, тому їх легко випадково змінити. Краще передавати значення через параметри, повертати результати та обмежувати область видимості змінних.
Execution context — внутрішня модель, у якій JavaScript виконує код.
Глобальний контекст створюється під час запуску скрипту або модуля.
Function Execution Context створюється для кожного виклику функції.
Контекст проходить фази створення та виконання.
Параметри, локальні змінні й зовнішні прив’язки утворюють лексичне оточення.
Контексти виконання зберігаються у call stack.
Після завершення функції її контекст зазвичай видаляється зі стеку.
Замикання можуть зберігати доступ до оточення завершеної функції.
this є частиною контексту, але його значення залежить від способу виклику функції.
Глобальний код, звичайні скрипти та ES-модулі можуть мати різні правила роботи з глобальними прив’язками.