Опанування основ JavaScript: контекст виконання, підняття змінних і області видимості

Розуміння основ JavaScript є ключем до оволодіння мовою. Концепції, такі як контекст виконання (Execution Context), підняття (Hoisting) і область видимості (Scoping), становлять основу того, як JavaScript працює "під капотом". Давайте глибше зануримося в ці поняття за допомогою зрозумілих пояснень та прикладів.

Контекст виконання (Execution Context) та стек викликів (Call Stack)

JavaScript є синхронною, однопотоковою мовою, яка виконує код рядок за рядком. Стек викликів (Call Stack) — це інша назва для стека контексту виконання (Execution Context Stack), що керує порядком виконання функцій.

Цей механізм також відомий під іншими назвами, такими як стек програми (Program Stack), стек управління (Control Stack), стек виконання (Runtime Stack) і машинний стек (Machine Stack).

Ось як це працює:
• Коли викликається функція, вона додається у верхню частину стека викликів.
• Коли виконання функції завершується, вона видаляється зі стека.
• Ця стекоподібна поведінка забезпечує впорядковане виконання коду.

Підняття (Hoisting) у JavaScript: змінні та функції

У JavaScript підняття (Hoisting) означає поведінку, коли оголошення змінних і функцій переміщуються на початок їхньої області видимості (Scope) під час фази компіляції. JavaScript обробляє код у два етапи:
1. Фаза виділення пам’яті (Memory Allocation Phase):
Змінні ініціалізуються значенням undefined, а функції зберігаються у своїй повній формі.
2.
Фаза виконання (Execution Phase):
Код виконується рядок за рядком.

console.log(x, y); // A: undefined undefined  

var x = 100, y = 200;  

console.log(sum(x, y)); // B: 300  

function sum(a, b) {  
 total = a + b; // 'total' стає глобальною змінною (не оголошена через var/let/const)  
 return a + b;  
}  

console.log(total); // C: 300

Що саме робить підняття (Hoisting)?

Вищенаведений код інтерпретується так:

var x;  
var y;  

function sum(a, b) {  
 total = a + b;  
 return a + b;  
}  

console.log(x, y); // A: undefined undefined  
x = 100; y = 200;  

console.log(sum(x, y)); // B: 300  

console.log(total); // C: 300

undefined vs. Not Defined у JavaScript

JavaScript є слабо типізованою мовою (Loosely Typed Language), тобто змінні не мають фіксованих типів.

  1. undefined: Змінна оголошена, але їй ще не присвоєно значення.
    2.
    Not defined: Змінна не була оголошена взагалі, що призводить до помилки ReferenceError.

Область видимості (Scope), ланцюжок областей видимості (Scope Chain) та лексичне середовище (Lexical Environment)

Область видимості — це місце, де змінна доступна для використання. У JavaScript під час звернення до змінних використовується ланцюжок областей видимості (Scope Chain).

function a() {  
 var b = 10;  
 c();  
 function c() {  
 console.log(b); // Доступ до 'b' із зовнішньої області видимості (функція 'a')  
 }  
}  

a();  
console.log(b); // ReferenceError: 'b' не визначено (змінна доступна лише в області функції 'a')

Функціональне вкладення: функція c лексично вкладена всередину a.
Функція c знайде змінну b у батьківській області видимості, шукаючи в усіх зовнішніх областях.

let і const із зоною тимчасової недоступності (Temporal Dead Zone, TDZ)

Змінні, оголошені через let і const, також піднімаються (Hoisted), але вони існують у зоні тимчасової недоступності (Temporal Dead Zone, TDZ) до моменту їх ініціалізації.
Доступ до змінних до їх ініціалізації призводить до помилки ReferenceError.

console.log(a); // ReferenceError  
let a = 10;

Блокова область видимості (Block Scope) та затінення (Shadowing)

JavaScript підтримує блокову область видимості (Block Scope) для let і const, але змінні, оголошені через var, мають функціональну область видимості (Function Scope).

var a = 100;  
{  
 var a = 10; // Затінює зовнішню змінну 'a'  
 console.log(a); // 10  
}  
console.log(a); // 10
const a = 20;  
{  
 const a = 100;   
 {  
 const a = 200;   
 console.log(a); // 200  
 }  
 console.log(a); // 100  
}  
console.log(a); // 20

Замикання (Closure) у JavaScript

Замикання (Closure) створюється, коли функція, після свого виконання, «запам’ятовує» своє лексичне середовище (Lexical Environment).
Це дозволяє використовувати потужні патерни, такі як збереження стану (State), інкапсуляція (Encapsulation) тощо.

function outest() {  
 var c = 20;  
 function outer(b) {  
 function inner() {  
 console.log(a, b, c); // Доступ до змінних із лексичного оточення  
 }  
 let a = 10;  
 return inner;  
 }  
 return outer;  
}  

var close = outest()("helloworld");  
close(); // 10 helloworld 20

Використання замикань (Closures):
• Патерн модулів (Module Design Pattern): Інкапсуляція приватних змінних.
• Карирування (Currying): Розбиття функції на декілька менших.
• Мемоізація (Memoization): Кешування результатів для оптимізації продуктивності.
• Збереження стану (State) у асинхронних операціях.
• Використання setTimeout і ітераторів (Iterators).

Недолік: Замикання (Closures) можуть призводити до надмірного використання пам’яті, якщо ними користуватися необережно.

Опанувавши ці концепції, ви зможете краще розуміти внутрішню роботу JavaScript і стати кращим розробником.

Перекладено з: Mastering JavaScript Foundations: Execution Context, Hoisting, and Scoping

Leave a Reply

Your email address will not be published. Required fields are marked *