Пошук уроків, статей та іншого контенту
Лексична область видимості, ланцюжок областей видимості та як замикання дозволяють приховувати стан.
Область видимості (scope) визначає, де в коді доступна змінна. JavaScript використовує лексичну область видимості: доступність змінної визначається тим, де функція написана в коді, а не звідки її викликали.
const greeting = "Привіт";
function sayHi() {
console.log(greeting); // бачить greeting, бо sayHi написана в тій самій області, де оголошено greeting
}
sayHi(); // "Привіт"Коли функція звертається до змінної, JavaScript шукає її спочатку в локальній області, потім у зовнішній, і так далі до глобальної — це і є scope chain:
const level1 = "зовнішня";
function outer() {
const level2 = "середня";
function inner() {
const level3 = "внутрішня";
console.log(level1, level2, level3); // бачить усі три
}
inner();
}Замикання (closure) — це функція разом зі змінними з її зовнішньої області видимості, які вона захопила на момент створення. Ключова властивість: ці змінні залишаються доступними навіть після того, як зовнішня функція завершила виконання:
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 — count не скидається між викликамиcreateCounter вже завершила виконання, коли ми викликаємо counter() — але внутрішня функція досі має доступ до count з її оточення. Це і є замикання в дії.
Замикання — основний спосіб емулювати приватні змінні до того, як у класах з'явились справжні приватні поля:
function createBankAccount(initialBalance) {
let balance = initialBalance; // недоступна ззовні напряму
return {
deposit(amount) {
balance += amount;
return balance;
},
getBalance() {
return balance;
},
};
}
const account = createBankAccount(100);
account.deposit(50);
console.log(account.getBalance()); // 150
console.log(account.balance); // undefined — прямого доступу немаєФункція, яка створює й повертає інші функції (або об'єкти з методами), — поширений патерн, що спирається саме на замикання. createCounter і createBankAccount вище — обидва приклади factory function.
Ми вже бачили цей приклад у попередньому модулі — тепер зрозуміло, чому саме так відбувається:
const callbacks = [];
for (var i = 0; i < 3; i++) {
callbacks.push(() => console.log(i));
}
callbacks.forEach((cb) => cb()); // 3, 3, 3
// var має функціональну область видимості — усі три функції замикають ОДНУ й ту саму i,
// яка на момент виклику вже дорівнює 3. let створює нову змінну на кожній ітерації —
// заміна var на let (як у попередньому модулі) вирішує проблему без жодних інших змін.Очікування, що зовнішня функція «скидає» захоплені змінні при повторному виклику — насправді кожен виклик createCounter() створює нове, незалежне замикання.
Створення замикань у циклі з var замість let без розуміння, чому це змінює поведінку.
Надмірне захоплення великих об'єктів у замиканні, коли потрібне лише одне поле — це затримує звільнення пам'яті (garbage collection), детальніше в модулі «Advanced JavaScript».
Область видимості визначається місцем у коді, де написана функція, а не де вона викликається. Замикання — це функція, яка «пам'ятає» змінні зі свого оточення навіть після завершення зовнішньої функції. Це основа для приватного стану, фабричних функцій і, як побачимо далі, для колбеків та асинхронного коду.
Спробуйте самостійно
Напишіть createToggle(), яка повертає функцію без аргументів: кожен виклик має повертати по черзі true, false, true, false... Використайте замикання для зберігання поточного стану.