Avatar ·

Область видимости переменных в JavaScript: var, let, const и функции

Всем привет! Я только начал изучать JavaScript и никак не могу разобраться с областью видимости переменных. Пишу простой код, но результаты меня постоянно удивляют. Вот пример: ```javascript var x = 10; function test() { var x = 20; if (true) { var x = 30; console.log(x); // выводит 30 } console.log(x); // выводит 30, а не 20! } test(); console.log(x); // выводит 10 ``` Почему внутри функции `test()` переменная `x` меняется, хотя я объявил её через `var` внутри блока `if`? Я думал, что `var` ограничен функцией, но тут он ведёт себя странно. Также я слышал про `let` и `const`, но не понимаю, чем они отличаются. Ещё один пример с замыканиями: ```javascript for (var i = 0; i < 3; i++) { setTimeout(function() { console.log(i); // выводит 3, 3, 3 }, 100); } ``` Почему все три вызова показывают 3, а не 0, 1, 2? Я ожидал, что каждый вызов setTimeout запомнит своё значение i. Объясните, пожалуйста, как работает область видимости и как правильно управлять переменными в JavaScript. Заранее спасибо! <<>>

Область видимости переменных в 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 только когда нужно переприсвоить значение. Это сделает ваш код предсказуемым и безопасным.