Затенение и лексическая область
Вернёмся в квартиру со стеклянными комнатами. Иногда в комнате стоит коробка с тем же именем, что и коробка в коридоре. Кто ищет вещь по имени, сначала осматривает свою комнату и берёт ближнюю коробку, а до коридора дело не доходит.
С переменными происходит то же самое. Имя, которое уже занято снаружи, можно объявить ещё раз во вложенном блоке. Внутри блока это имя означает новую переменную, а внешняя переменная остаётся прежней:
let level = 1;
if (level > 0) {
let level = 5;
console.log(level); // 5
}
console.log(level); // 1Давайте разберём программу по строкам:
let level = 1создаёт внешнюю переменную.- Условие
level > 0истинно, иlet level = 5создаёт вторую переменную внутри блока. - Первый
console.log(level)печатает внутреннюю переменную:5. - После
}внутренней переменной уже нет, и второйconsole.logпечатает внешнюю:1.
Затенение
Внутренний let level создаёт вторую переменную, которая живёт до закрывающей скобки блока. Пока внутренняя переменная существует, она закрывает внешнюю, и имя level внутри блока означает внутреннюю. Это называют затенением.
Внешнюю переменную блок не меняет, и после } имя снова означает её.
Здесь картинка с комнатами перестаёт работать. Сквозь стекло из комнаты видна и дальняя коробка в коридоре. А в программе до конца блока имя level означает только внутреннюю переменную, и добраться по нему до внешней нельзя.
Одно имя дважды
Можно ли объявить одно имя второй раз, зависит от того, где стоит второе объявление.
В одном блоке
Объявить одно имя дважды через let или const в одном и том же блоке нельзя. Это синтаксическая ошибка: Node.js находит её ещё до запуска и не выполняет ни одной строки программы.
let x = 1;
let x = 2; // ✗SyntaxError: Identifier 'x' has already been declaredПовторный var с тем же именем ошибкой не считается: после var x = 1; var x = 2; программа работает дальше, и x хранит 2.
Во вложенном блоке
Во вложенном блоке, даже в отдельной паре {}, второе объявление через let или const допустимо: там своя область видимости. Затенение — не ошибка, но читать такой код трудно. Внутренней переменной лучше дать другое имя.
Где написана функция
Функция в нашей картинке — тоже комната, построенная в определённом месте квартиры. Из её окон видно то, что окружает место постройки, и откуда бы её ни позвали, вид из окон не меняется.
Функция видит переменные того места, где она написана, а не того, откуда её вызывают. Это правило называют лексической областью видимости: видимость определяет текст программы, а не порядок вызовов.
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 видит каждая функция:
greetнаписана на верхнем уровне файла и видит внешнюю константуgreetingсо значением"Hello".- Константа
greetingвнутриrunвидна только в телеrun. Текстgreetв это тело не входит, поэтому вызов изrunна результатgreetне влияет. innerнаписана внутриrun, поэтому видит константуgreetingизrunсо значением"Hi".runсклеивает оба результата через пробел, и программа печатаетHello Hi.
Обе функции вызваны из одного места, из тела run, а возвращают разное. Решает только то, где написан текст функции.
На собеседовании это часто проверяют задачей «что напечатает программа», похожей на пример выше. Ищите не место вызова, а место, где написана функция.
Поиск имени
Имя ищется изнутри наружу:
- в блоке, где стоит обращение;
- в окружающих блоках и функциях;
- на верхнем уровне файла.
Так функция, написанная внутри другой функции, видит и её переменные, и переменные файла. Поиск останавливается на первом найденном имени, поэтому и работает затенение: inner находит greeting в run и до верхнего уровня файла не доходит.
Частая ошибка
Во вложенном блоке хотели изменить внешнюю переменную, но написали let:
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 и подпишись на канал — откроются все уроки.