Область видимости переменных в JavaScript: var, let, const и функции
Область видимости переменных в JavaScript: полное руководство
Вы столкнулись с классическими проблемами, которые возникают у всех новичков в JavaScript. Давайте разберём всё по порядку.
1. Три типа области видимости
В JavaScript существует три уровня области видимости:
- Глобальная — переменные, объявленные вне любых функций или блоков.
- Функциональная — переменные, доступные только внутри функции, где они объявлены.
- Блочная — переменные, доступные только внутри блока
{}(появилась с ES6).
2. Var: функциональная область видимости
var имеет функциональную область видимости. Это значит, что переменная, объявленная через var, видна во всей функции, независимо от того, в каком блоке она объявлена. Именно поэтому в вашем первом примере:
function test() {
var x = 20; // переменная x видна во всей функции
if (true) {
var x = 30; // это та же самая переменная x, переопределение
console.log(x); // 30
}
console.log(x); // 30, потому что var игнорирует блоки
}
Переменная x внутри if — это та же самая переменная, что и в начале функции. Блок if не создаёт новой области видимости для var.
3. Let и const: блочная область видимости
В отличие от var, let и const имеют блочную область видимости. Они видны только внутри того блока {}, где объявлены:
function test() {
let x = 20; // переменная x видна во всей функции
if (true) {
let x = 30; // это НОВАЯ переменная, только внутри if
console.log(x); // 30
}
console.log(x); // 20, потому что let уважает блоки
}
test();
Разница между let и const: const нельзя переприсвоить (но можно изменять свойства объекта).
4. Проблема с setTimeout и var
Ваш второй пример — классическая проблема с замыканиями и var:
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 3, 3, 3
}, 100);
}
Почему так происходит? Потому что var i имеет функциональную область видимости (в данном случае глобальную). Все три функции setTimeout ссылаются на одну и ту же переменную i. К моменту их выполнения (через 100 мс) цикл уже завершился, и i равна 3.
5. Решения проблемы с setTimeout
Есть три способа решить эту проблему:
Способ 1: Использовать let (рекомендуется)
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2
}, 100);
}
let создаёт новую переменную i для каждой итерации цикла.
Способ 2: Использовать замыкание через IIFE
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 0, 1, 2
}, 100);
})(i);
}
Создаётся новая функция, которая "запоминает" текущее значение i через параметр j.
Способ 3: Использовать forEach
[0, 1, 2].forEach(function(i) {
setTimeout(function() {
console.log(i); // 0, 1, 2
}, 100);
});
6. Практические рекомендации
- Всегда используйте
letиconstвместоvarв современном коде. - По умолчанию используйте
const, если переменная не должна переназначаться. - Используйте
let, когда нужно переприсвоить значение. - Избегайте
varв новом коде — он устарел и создаёт путаницу.
Запомните главное: var — функциональная область видимости, let/const — блочная область видимости. Это знание решит 90% ваших проблем с областью видимости.
Дополнение: поднятие (hoisting) и временная мёртвая зона
Чтобы полностью понять область видимости, нужно разобраться с двумя важными концепциями: поднятие (hoisting) и временная мёртвая зона (Temporal Dead Zone, TDZ).
Поднятие (hoisting) для var
Переменные, объявленные через var, "поднимаются" в начало своей области видимости (функции или глобального контекста). Это означает, что вы можете обратиться к переменной до её объявления — она будет undefined:
function example() {
console.log(x); // undefined, а не ошибка!
var x = 10;
console.log(x); // 10
}
JavaScript интерпретирует этот код так:
function example() {
var x; // объявление поднято наверх
console.log(x); // undefined
x = 10; // присваивание остаётся на месте
console.log(x); // 10
}
Временная мёртвая зона (TDZ) для let и const
В отличие от var, let и const тоже поднимаются, но не инициализируются. До момента фактического объявления переменная находится в "временной мёртвой зоне" — любое обращение к ней вызывает ошибку ReferenceError:
function example() {
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 10;
}
Это защищает от случайного использования переменной до её инициализации.
Практический совет
Чтобы избежать путаницы, всегда объявляйте переменные в начале их области видимости. Для let и const это особенно важно из-за TDZ. Используйте const по умолчанию и меняйте на let только когда нужно переприсвоить значение. Это сделает ваш код предсказуемым и безопасным.
Ответов пока нет.
Войдите чтобы оставить ответ