Пошук уроків, статей та іншого контенту
Розберіть блокову та функціональну області видимості й дізнайтеся, як вони впливають на доступність змінних. Порівняйте поведінку var, let і const усередині функцій та блоків.
Область видимості визначає, де саме в коді можна звернутися до змінної або константи.
JavaScript використовує лексичну область видимості: доступність змінної залежить від того, де вона оголошена в тексті програми, а не від того, звідки викликається функція.
Основні типи областей видимості:
глобальна — змінна доступна майже в усьому скрипті;
функціональна — змінна доступна всередині функції;
блокова — змінна доступна всередині блоку { ... }.
Блоком вважається код у фігурних дужках:
{
// Це блок
}
if (true) {
// Це також блок
}
for (let i = 0; i < 3; i++) {
// І тіло циклу є блоком
}Вкладена область видимості може звертатися до змінних із зовнішньої області:
const language = "JavaScript";
function showLanguage() {
const message = `Мова: ${language}`;
console.log(message);
}
showLanguage();Функція showLanguage не має власної змінної language, тому JavaScript шукає її у зовнішній області.
Пошук відбувається від внутрішньої області до зовнішньої:
JavaScript шукає змінну в поточній області;
якщо не знаходить — переходить до зовнішньої;
пошук продовжується до глобальної області;
якщо змінну не знайдено ніде, виникає ReferenceError.
const outerValue = "зовнішнє значення";
function outerFunction() {
const innerValue = "внутрішнє значення";
function innerFunction() {
console.log(innerValue);
console.log(outerValue);
}
innerFunction();
}
outerFunction();Внутрішня функція має доступ і до innerValue, і до outerValue.
Зворотне правило не працює: зовнішня область не має доступу до змінних із внутрішньої.
function createMessage() {
const message = "Привіт";
}
createMessage();
console.log(message); // ReferenceErrorvarЗмінні, оголошені через var, мають функціональну область видимості.
Це означає, що вони доступні в усій функції, де були оголошені, але не виходять за межі цієї функції.
function processData() {
var status = "готово";
console.log(status); // "готово"
}
processData();
console.log(status); // ReferenceErrorДля var блоки if, for або звичайні блоки не створюють окрему область видимості:
function checkStatus() {
if (true) {
var status = "активний";
}
console.log(status); // "активний"
}
checkStatus();Змінна status оголошена всередині if, але доступна в усій функції checkStatus.
Якщо var оголошено не всередині функції, воно потрапляє до глобальної області:
var globalValue = 10;
console.log(globalValue); // 10У сучасному JavaScript для змінних, які мають бути локальними, зазвичай використовують let або const. var зберігає історичну поведінку й може призводити до неочікуваних результатів.
letЗмінні, оголошені через let, мають блокову область видимості.
Вони доступні лише між фігурними дужками, у яких були оголошені:
if (true) {
let message = "Всередині блоку";
console.log(message); // "Всередині блоку"
}
console.log(message); // ReferenceErrorОкремий блок також створює область видимості:
{
let value = 42;
console.log(value); // 42
}
console.log(value); // ReferenceErrorБлокова область допомагає ізолювати тимчасові змінні та зменшує ризик конфліктів імен:
let result = "зовнішній";
{
let result = "внутрішній";
console.log(result); // "внутрішній"
}
console.log(result); // "зовнішній"Тут існують дві різні змінні з ім’ям result. Внутрішня змінна затіняє зовнішню лише всередині свого блоку.
constconst, як і let, має блокову область видимості:
function calculateTotal() {
if (true) {
const total = 150;
console.log(total); // 150
}
// console.log(total); // Помилка: total недоступна за межами блоку
}
calculateTotal();Основна відмінність const від let — неможливість повторно присвоїти значення після оголошення:
const maxUsers = 100;
// maxUsers = 200; // TypeErrorОднак const не робить об’єкт або масив повністю незмінним. Заборонено змінювати саме посилання на об’єкт, але його властивості можна змінювати:
const user = {
name: "Олена"
};
user.name = "Марія";
console.log(user.name); // "Марія"
// user = {}; // TypeErrorДля примітивних значень const фактично забороняє повторне присвоєння:
const count = 1;
// count = 2; // TypeErrorvar, let і const| Властивість | var | let | const | |---|---|---|---| | Область видимості | функціональна | блокова | блокова | | Повторне оголошення в тій самій області | дозволене | заборонене | заборонене | | Повторне присвоєння | дозволене | дозволене | заборонене | | Підняття оголошення | так, зі значенням undefined | так, але діє TDZ | так, але діє TDZ |
На практиці найчастіше використовують такий порядок:
const — якщо змінну не потрібно переназначати;
let — якщо значення змінюватиметься;
var — переважно для підтримки старого коду.
Змінна у внутрішній області може мати таке саме ім’я, як змінна у зовнішній області:
const value = "глобальне значення";
function showValue() {
const value = "локальне значення";
console.log(value); // "локальне значення"
}
showValue();
console.log(value); // "глобальне значення"Внутрішня змінна value затіняє зовнішню в межах функції.
Затінення дозволене, якщо змінні належать до різних областей:
let message = "зовнішній";
{
let message = "внутрішній";
console.log(message); // "внутрішній"
}
console.log(message); // "зовнішній"Але повторне оголошення в одній і тій самій області заборонене для let і const:
let name = "Олена";
// let name = "Марія"; // SyntaxErrorОголошення var також не можна поєднувати з let або const в одній області:
var value = 1;
// let value = 2; // SyntaxErrorКожен виклик функції створює власне функціональне оточення:
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Функція increment зберігає доступ до змінної count, навіть після завершення createCounter. Такий механізм називається замиканням.
Для розуміння областей видимості важливо пам’ятати: функція запам’ятовує область, у якій була створена.
const prefix = "Результат";
function createFormatter() {
const separator = ": ";
return function format(value) {
return prefix + separator + value;
};
}
const format = createFormatter();
console.log(format("успішно")); // "Результат: успішно"let і const добре працюють у циклах, оскільки кожна ітерація може мати власне значення змінної.
const callbacks = [];
for (let i = 0; i < 3; i += 1) {
callbacks.push(() => i);
}
console.log(callbacks[0]()); // 0
console.log(callbacks[1]()); // 1
console.log(callbacks[2]()); // 2У цьому прикладі кожна функція отримує значення i, яке відповідає конкретній ітерації.
Історично подібний код із var часто спричиняв помилки:
const callbacks = [];
for (var i = 0; i < 3; i += 1) {
callbacks.push(() => i);
}
console.log(callbacks[0]()); // 3
console.log(callbacks[1]()); // 3
console.log(callbacks[2]()); // 3var має функціональну область видимості, тому всі функції посилаються на одну змінну i. Після завершення циклу її значення дорівнює 3.
JavaScript обробляє оголошення змінних до виконання коду. Цей процес часто називають підняттям або hoisting.
Для var змінна створюється зі значенням undefined:
console.log(value); // undefined
var value = 10;
console.log(value); // 10Фактично JavaScript сприймає цей код приблизно так:
var value;
console.log(value); // undefined
value = 10;
console.log(value); // 10let і const також піднімаються, але до моменту виконання оголошення перебувають у тимчасовій мертвій зоні — Temporal Dead Zone, або TDZ.
// console.log(value); // ReferenceError
let value = 10;До рядка з оголошенням value звертатися не можна.
Те саме стосується const:
// console.log(apiUrl); // ReferenceError
const apiUrl = "https://example.com";TDZ допомагає швидше виявляти помилки, коли змінну використовують до її оголошення.
ifРозглянемо різницю між var, let і const у блоці if:
function compareScopes(isActive) {
if (isActive) {
var varMessage = "var доступна у функції";
let letMessage = "let доступна лише в блоці";
const constMessage = "const доступна лише в блоці";
console.log(varMessage);
console.log(letMessage);
console.log(constMessage);
}
console.log(varMessage);
// console.log(letMessage); // ReferenceError
// console.log(constMessage); // ReferenceError
}
compareScopes(true);varMessage доступна після if, тому що область видимості var — функція.
letMessage і constMessage доступні тільки всередині блоку if.
Уявімо функцію, яка формує звіт про замовлення:
function createOrderReport(order) {
const currency = "₴";
let status = "нове";
if (order.paid) {
status = "оплачено";
}
{
const formattedTotal = `${order.total} ${currency}`;
return {
id: order.id,
status,
total: formattedTotal
};
}
}
const report = createOrderReport({
id: 101,
total: 1250,
paid: true
});
console.log(report);
// { id: 101, status: "оплачено", total: "1250 ₴" }У цьому прикладі:
currency доступна в усій функції;
status доступна в усій функції та може змінюватися;
formattedTotal доступна лише у внутрішньому блоці;
після виконання return зовнішній код не може напряму звернутися до formattedTotal.
У скрипті, підключеному звичайним способом, глобальні змінні можуть впливати на весь код сторінки.
У JavaScript-модулях змінні верхнього рівня мають модульну область видимості та не стають властивостями глобального об’єкта автоматично:
const moduleName = "orders";
// У межах цього модуля змінна доступна.
// Вона не створює автоматично window.moduleName у браузері.Модулі допомагають ізолювати код і зменшують ризик випадкових конфліктів глобальних імен.
Незалежно від типу файлу, хороша практика — не створювати зайві глобальні змінні. Краще обмежувати область видимості функцією, модулем або блоком.
varif (true) {
var userRole = "admin";
}
console.log(userRole); // "admin"Якщо змінна не повинна бути доступною після блоку, використовуйте let або const:
if (true) {
const userRole = "admin";
}
// console.log(userRole); // ReferenceError// console.log(total); // ReferenceError
const total = 100;Оголошуйте змінні до першого використання. Це робить порядок виконання очевидним і запобігає помилкам TDZ.
let status = "pending";
function updateStatus() {
let status = "completed";
console.log(status);
}
updateStatus();
console.log(status); // "pending"Функція змінила лише локальну змінну, а не зовнішню. Варто використовувати різні та зрозумілі імена, якщо затінення не є навмисним.
function calculate() {
if (true) {
const result = 42;
}
// console.log(result); // ReferenceError
}Якщо значення потрібне після блоку, оголосіть змінну у зовнішній області:
function calculate() {
let result;
if (true) {
result = 42;
}
console.log(result); // 42
}
calculate();let currentUser;
let selectedProduct;
let cartTotal;Глобальний стан складніше контролювати, тестувати й змінювати. За можливості оголошуйте дані в найбільш вузькій області, де вони потрібні.
Використовуйте const за замовчуванням.
Використовуйте let, якщо змінну потрібно переназначати.
У новому коді уникайте var.
Оголошуйте змінні в найвужчій можливій області.
Не створюйте глобальні змінні без необхідності.
Слідкуйте за затіненням імен.
Не використовуйте змінну до її оголошення.
Пам’ятайте, що функції мають власну область видимості, а блоки впливають на let і const, але не на var.
var має функціональну область видимості.
let і const мають блокову область видимості.
Вкладена область може звертатися до змінних зовнішньої області.
Зовнішня область не має доступу до локальних змінних вкладеної області.
let можна переназначати, а const — ні.
var доступна після завершення блоку, у якому її оголосили, якщо цей блок не є функцією.
let і const перебувають у TDZ до моменту фактичного оголошення.
У циклах let зазвичай безпечніша за var, особливо під час створення замикань.
Найчастіше в сучасному JavaScript слід використовувати const і let, обмежуючи область видимості змінних.