Пошук уроків, статей та іншого контенту
Дізнайтеся, як JavaScript шукає ідентифікатор спочатку в поточній області видимості, а потім послідовно переходить до зовнішніх. Розберіть Scope Chain на прикладах вкладених функцій і блоків.
Область видимості (scope) — це частина програми, у якій ідентифікатор доступний для використання.
Ідентифікатором може бути:
змінна;
константа;
параметр функції;
ім’я функції;
ім’я класу.
JavaScript використовує лексичну область видимості. Це означає, що доступність змінної визначається структурою коду, а не місцем виклику функції.
const language = "JavaScript";
function showLanguage() {
console.log(language);
}
showLanguage(); // JavaScriptФункція showLanguage не має локальної змінної language, тому JavaScript шукає її у зовнішній області видимості.
Scope Chain — це послідовність областей видимості, яку JavaScript проходить під час пошуку ідентифікатора.
Пошук відбувається так:
JavaScript шукає ідентифікатор у поточній області видимості.
Якщо не знаходить, переходить до зовнішньої області.
Продовжує пошук угору по вкладених областях.
Якщо доходить до глобальної області й не знаходить ідентифікатор, виникає ReferenceError.
Наприклад:
const globalValue = "global";
function outer() {
const outerValue = "outer";
function inner() {
const innerValue = "inner";
console.log(innerValue); // inner
console.log(outerValue); // outer
console.log(globalValue); // global
}
inner();
}
outer();Для innerValue пошук завершується одразу в області inner.
Для outerValue JavaScript перевіряє:
область inner — змінної немає;
область outer — змінну знайдено.
Для globalValue пошук проходить усі рівні:
inner — немає;
outer — немає;
глобальна область — знайдено.
Умовно ланцюжок має такий вигляд:
inner → outer → globalКожна функція створює власну функціональну область видимості. Вкладена функція має доступ до змінних зовнішньої функції.
function createMessage() {
const name = "Олена";
function getMessage() {
return `Привіт, ${name}!`;
}
return getMessage();
}
console.log(createMessage()); // Привіт, Олена!Функція getMessage не оголошує змінну name, тому JavaScript знаходить її в createMessage.
Зворотний напрямок не працює:
function outer() {
function inner() {
const secret = "значення";
}
inner();
console.log(secret); // ReferenceError
}
outer();Зовнішня функція не має доступу до локальних змінних внутрішньої функції. Scope Chain працює зсередини назовні, а не навпаки.
Параметри та змінні, оголошені всередині функції, доступні лише в цій функції та її вкладених функціях.
const value = "global";
function example(value) {
console.log(value);
}
example("local"); // local
console.log(value); // globalПараметр value створює локальну змінну функції. Він не змінює глобальну змінну з таким самим іменем.
Змінні, оголошені через let і const, мають блокову область видимості. Блоком є код усередині:
фігурних дужок {};
if;
for;
while;
switch;
try і catch.
const outside = "зовні";
if (true) {
const inside = "всередині";
console.log(outside); // зовні
console.log(inside); // всередині
}
console.log(outside); // зовні
// console.log(inside); // ReferenceErrorБлок може використовувати змінні з зовнішньої області, але зовнішня область не бачить змінні, оголошені всередині блока.
for (let index = 0; index < 3; index++) {
console.log(index);
}
// console.log(index); // ReferenceErrorindex доступний лише всередині циклу.
Те саме стосується змінної, оголошеної в тілі циклу:
for (let index = 0; index < 3; index++) {
const message = `Ітерація ${index}`;
console.log(message);
}
// console.log(message); // ReferenceErrorvar і область видимостіНа відміну від let і const, змінна, оголошена через var, має функціональну, а не блокову область видимості.
function example() {
if (true) {
var message = "готово";
}
console.log(message); // готово
}
example();Блок if не створив окрему область для message, тому змінна доступна в усій функції.
Проте за межами функції вона недоступна:
function example() {
var value = 10;
}
example();
// console.log(value); // ReferenceErrorУ сучасному JavaScript зазвичай варто використовувати const і let:
const — якщо змінна не перевизначатиметься;
let — якщо значення потрібно змінювати;
var — переважно в коді, який підтримує старі підходи або специфічну поведінку.
Якщо в різних областях видимості є змінні з однаковим іменем, внутрішня змінна затінює зовнішню.
const message = "глобальне значення";
function showMessage() {
const message = "локальне значення";
console.log(message); // локальне значення
}
showMessage();
console.log(message); // глобальне значенняПід час пошуку JavaScript знаходить локальну message першою і не продовжує пошук зовнішньої змінної.
Затінення може відбуватися на кількох рівнях:
const value = "global";
function outer() {
const value = "outer";
function inner() {
const value = "inner";
console.log(value); // inner
}
inner();
}
outer();У функції inner доступна саме найближча змінна value.
let status = "зовнішній";
{
let status = "внутрішній";
console.log(status); // внутрішній
}
console.log(status); // зовнішнійЦе дві різні змінні, хоча вони мають однакове ім’я.
Важливо не плутати лексичну область видимості з місцем, де функцію викликали.
const value = "global";
function getValue() {
return value;
}
function run() {
const value = "local";
return getValue();
}
console.log(run()); // globalПід час створення getValue її зовнішньою областю була глобальна область. Тому функція шукає value саме там.
Вона не використовує value із run, хоча виклик getValue() відбувається всередині run.
Це і є наслідок лексичної області видимості: Scope Chain формується структурою оголошення функції.
Замикання (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Після завершення createCounter змінна count не зникає, оскільки внутрішня функція increment продовжує мати до неї доступ через Scope Chain.
Можна створити незалежні лічильники:
function createCounter() {
let count = 0;
return () => {
count += 1;
return count;
};
}
const firstCounter = createCounter();
const secondCounter = createCounter();
console.log(firstCounter()); // 1
console.log(firstCounter()); // 2
console.log(secondCounter()); // 1Кожен виклик createCounter створює власне лексичне оточення з окремою змінною count.
Під час кожного виклику функції створюється нова локальна область. Вона містить:
параметри;
локальні змінні;
оголошення функцій;
посилання на зовнішню область видимості.
Розглянемо повний приклад:
const appName = "Планувальник";
function createTaskManager(userName) {
const tasks = [];
function addTask(taskName) {
tasks.push(taskName);
}
function getSummary() {
return `${userName}: ${tasks.length} завдань у застосунку "${appName}"`;
}
return {
addTask,
getSummary
};
}
const manager = createTaskManager("Андрій");
manager.addTask("Вивчити Scope Chain");
manager.addTask("Написати вправу");
console.log(manager.getSummary());
// Андрій: 2 завдань у застосунку "Планувальник"Коли виконується getSummary, пошук змінних відбувається так:
tasks знаходиться в createTaskManager;
userName знаходиться в createTaskManager;
appName знаходиться в глобальній області.
Метод getSummary продовжує бачити ці змінні навіть після завершення початкового виклику createTaskManager.
Якщо ідентифікатор відсутній у всіх доступних областях, JavaScript викидає ReferenceError.
function showValue() {
console.log(notDefined);
}
showValue(); // ReferenceError: notDefined is not definedЦе відрізняється від змінної зі значенням undefined:
let declaredValue;
console.log(declaredValue); // undefined
// console.log(notDeclared); // ReferenceErrorУ першому випадку змінна існує, але не має явного значення. У другому ідентифікатор взагалі не знайдено.
Щоб визначити, яке значення отримає змінна, можна діяти так:
Знайдіть місце, де використовується ідентифікатор.
Перевірте поточний блок або функцію.
Перевірте параметри та локальні оголошення.
Перейдіть до зовнішньої функції або блока.
Повторюйте пошук до глобальної області.
Переконайтеся, що ближча змінна не затінює зовнішню.
Наприклад:
const color = "червоний";
function outer() {
const color = "зелений";
if (true) {
const color = "синій";
console.log(color);
}
}
outer(); // синійДля console.log(color) пошук завершується в блоці if, оскільки саме там знайдено найближчу змінну.
function outer() {
function inner() {
const value = 42;
}
// console.log(value); // ReferenceError
}Внутрішня область видимості не передає свої змінні назовні.
let і varif (true) {
let letValue = 1;
var varValue = 2;
}
// console.log(letValue); // ReferenceError
console.log(varValue); // 2let має блокову область, а var — функціональну.
const name = "глобальне";
function printName() {
return name;
}
function run() {
const name = "локальне";
return printName();
}
console.log(run()); // глобальнеФункція використовує область, у якій її оголосили, а не область, у якій викликали.
const user = "глобальний користувач";
function process() {
const user = "локальний користувач";
if (user) {
const user = "користувач блока";
console.log(user);
}
}Такий код працює, але однакові імена на різних рівнях ускладнюють читання. Якщо затінення не має чіткої причини, краще використовувати точніші імена.
function example() {
// console.log(value); // ReferenceError через temporal dead zone
const value = 10;
}Для let і const область змінної вже відома, але звертатися до неї до виконання оголошення не можна.
Scope визначає, де ідентифікатор доступний.
Scope Chain — це ланцюжок поточної та зовнішніх областей видимості.
Пошук завжди починається з найближчої області й рухається назовні.
Вкладені функції мають доступ до змінних зовнішніх функцій.
Зовнішні області не мають доступу до локальних змінних вкладених функцій.
let і const мають блокову область видимості.
var має функціональну область видимості.
Ближча змінна затінює змінну з таким самим іменем у зовнішній області.
Функція використовує лексичне оточення місця свого оголошення, а не місця виклику.
Замикання зберігають доступ до змінних зовнішньої області після завершення її виконання.
Якщо ідентифікатор не знайдено в усьому Scope Chain, JavaScript викидає ReferenceError.