Перейти к содержимому
Теория · Область видимости · урок 5 из 8

Затенение и лексическая область

Вернёмся в квартиру со стеклянными комнатами. Иногда в комнате стоит коробка с тем же именем, что и коробка в коридоре. Кто ищет вещь по имени, сначала осматривает свою комнату и берёт ближнюю коробку, а до коридора дело не доходит.

С переменными происходит то же самое. Имя, которое уже занято снаружи, можно объявить ещё раз во вложенном блоке. Внутри блока это имя означает новую переменную, а внешняя переменная остаётся прежней:

JavaScript
let level = 1;

if (level > 0) {
    let level = 5;
    console.log(level); // 5
}

console.log(level); // 1

Давайте разберём программу по строкам:

  1. let level = 1 создаёт внешнюю переменную.
  2. Условие level > 0 истинно, и let level = 5 создаёт вторую переменную внутри блока.
  3. Первый console.log(level) печатает внутреннюю переменную: 5.
  4. После } внутренней переменной уже нет, и второй console.log печатает внешнюю: 1.

Затенение

Внутренний let level создаёт вторую переменную, которая живёт до закрывающей скобки блока. Пока внутренняя переменная существует, она закрывает внешнюю, и имя level внутри блока означает внутреннюю. Это называют затенением.

Внешнюю переменную блок не меняет, и после } имя снова означает её.

Здесь картинка с комнатами перестаёт работать. Сквозь стекло из комнаты видна и дальняя коробка в коридоре. А в программе до конца блока имя level означает только внутреннюю переменную, и добраться по нему до внешней нельзя.

Одно имя дважды

Можно ли объявить одно имя второй раз, зависит от того, где стоит второе объявление.

В одном блоке

Объявить одно имя дважды через let или const в одном и том же блоке нельзя. Это синтаксическая ошибка: Node.js находит её ещё до запуска и не выполняет ни одной строки программы.

JavaScript
let x = 1;
let x = 2; // ✗
TEXT
SyntaxError: Identifier 'x' has already been declared

Повторный var с тем же именем ошибкой не считается: после var x = 1; var x = 2; программа работает дальше, и x хранит 2.

Во вложенном блоке

Во вложенном блоке, даже в отдельной паре {}, второе объявление через let или const допустимо: там своя область видимости. Затенение — не ошибка, но читать такой код трудно. Внутренней переменной лучше дать другое имя.

Где написана функция

Функция в нашей картинке — тоже комната, построенная в определённом месте квартиры. Из её окон видно то, что окружает место постройки, и откуда бы её ни позвали, вид из окон не меняется.

Функция видит переменные того места, где она написана, а не того, откуда её вызывают. Это правило называют лексической областью видимости: видимость определяет текст программы, а не порядок вызовов.

JavaScript
const greeting = "Hello";

function greet() {
    return greeting;
}

function run() {
    const greeting = "Hi";
    function inner() {
        return greeting;
    }
    return greet() + " " + inner();
}

console.log(run()); // Hello Hi

Разберём, какую greeting видит каждая функция:

  1. greet написана на верхнем уровне файла и видит внешнюю константу greeting со значением "Hello".
  2. Константа greeting внутри run видна только в теле run. Текст greet в это тело не входит, поэтому вызов из run на результат greet не влияет.
  3. inner написана внутри run, поэтому видит константу greeting из run со значением "Hi".
  4. run склеивает оба результата через пробел, и программа печатает Hello Hi.

Обе функции вызваны из одного места, из тела run, а возвращают разное. Решает только то, где написан текст функции.

На собеседовании это часто проверяют задачей «что напечатает программа», похожей на пример выше. Ищите не место вызова, а место, где написана функция.

Поиск имени

Имя ищется изнутри наружу:

  1. в блоке, где стоит обращение;
  2. в окружающих блоках и функциях;
  3. на верхнем уровне файла.

Так функция, написанная внутри другой функции, видит и её переменные, и переменные файла. Поиск останавливается на первом найденном имени, поэтому и работает затенение: inner находит greeting в run и до верхнего уровня файла не доходит.

Частая ошибка

Во вложенном блоке хотели изменить внешнюю переменную, но написали let:

JavaScript
let message = "none";
const score = 70;

if (score > 50) {
    let message = "pass"; // ✗
}

console.log(message); // none

Ошибки нет, а программа печатает none: let создал внутреннюю переменную, которая исчезла на }. Чтобы изменить внешнюю переменную, напишите присваивание без let: message = "pass";.

Главное

  • Переменная с тем же именем во вложенном блоке затеняет внешнюю до конца блока, а внешняя не меняется.
  • Дважды объявить одно имя через let или const в одном блоке нельзя: это SyntaxError.
  • Функция видит переменные места, где она написана, а не места, откуда её вызвали.
  • Имя ищется изнутри наружу, и поиск останавливается на первом найденном.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом