Розуміння основ JavaScript є ключем до опанування цієї мови. Такі концепції, як контекст виконання, підняття (hoisting) та області видимості (scoping), є основою того, як JavaScript працює за лаштунками. Давайте детально розглянемо ці концепції з чіткими поясненнями та прикладами.
Контекст виконання та стек викликів
JavaScript — це синхронна мова з одною ниткою виконання, яка виконує код по черзі, рядок за рядком. Стек викликів (Call Stack) — це інша назва для стека контексту виконання, який керує порядком виконання функцій.
Цей стек також має інші назви, такі як стек програми (Program Stack), контрольний стек (Control Stack), стек виконання (Runtime Stack) та машинний стек (Machine Stack).
Ось як це працює:
• Коли викликається функція, вона додається до верху стека викликів.
• Коли функція завершується, вона видаляється з цього стеку.
```js
function example() {
console.log("Hello, world!");
}
example();
• Така поведінка, схожа на стек, забезпечує впорядковане виконання коду.
Підняття (Hoisting) в JavaScript: змінні та функції
В JavaScript підняття (hoisting) — це поведінка, коли оголошення змінних та функцій переміщуються на верхівку своєї області видимості (scope) під час фази компіляції. JavaScript обробляє код у два етапи:
1. Етап виділення пам'яті:
Змінні ініціалізуються значенням undefined, а функції зберігаються повністю.
2.
```js
console.log(myVar); // undefined
var myVar = 5;
Етап виконання (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 — це мова з вільною типізацією, що означає, що змінні не мають фіксованих типів.
- undefined: Змінна оголошена, але їй не присвоєно значення.
-
```js
let myVar;
console.log(myVar); // undefined
Не оголошена (Not defined): Змінна зовсім не оголошена, що призводить до виникнення помилки ReferenceError.
Область видимості (Scope), ланцюг областей видимості (Scope Chain) та лексичне середовище (Lexical Environment)
Області видимості визначають, де змінна може існувати. В JavaScript, коли відбувається оцінка посилань на змінні, існує ланцюг областей видимості.
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 з тимчасовою мертвою зоною (TDZ)
Змінні, оголошені через let та const, підлягають підняттю (hoisting), але вони існують у Тимчасовій Мертвій Зоні (Temporal Dead Zone, TDZ) до того, як вони будуть ініціалізовані.
```js
let a;
console.log(a); // ReferenceError: a is in TDZ
a = 10;
Доступ до них до ініціалізації призводить до помилки ReferenceError.
console.log(a); // ReferenceError
let a = 10;
Область видимості блоку (Block Scope) та затінення (Shadowing)
JavaScript підтримує області видимості на рівні блоку для let та const, але var має область видимості на рівні функції.
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
Замикання створюється, коли функція після виконання «пам’ятає» своє лексичне середовище.
```js
function outer() {
let outerVar = "I'm outside!";
function inner() {
console.log(outerVar); // Доступ до змінної з зовнішнього середовища
}
return inner;
}
const closure = outer();
closure(); // "I'm outside!"
Це дозволяє створювати потужні патерни, такі як збереження стану, інкапсуляція тощо.
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):
• Патерн проектування модуля: Інкапсуляція приватних змінних.
• Каріування (Currying): Розбиття функції на кілька менших функцій.
• Мемоізація: Кешування результатів для оптимізації продуктивності.
• Збереження стану в асинхронних операціях.
• setTimeout та ітератори (Iterators).
Недолік: Замикання можуть призвести до надмірного споживання пам'яті, якщо не використовувати їх обережно.
Оволодівши цими концепціями, ви зможете краще зрозуміти внутрішню роботу JavaScript і, таким чином, стати кращим розробником.
```js
function memoize(func) {
const cache = {};
return function(x) {
if (x in cache) {
return cache[x];
} else {
const result = func(x);
cache[x] = result;
return result;
}
};
}
Перекладено з: Mastering JavaScript Foundations: Execution Context, Hoisting, and Scoping