Область видимости и hoisting
Вопрос
Чем let отличается от var, и что такое temporal dead zone?
Короткий ответ
var имеет функциональную область видимости и всплывает: объявление поднимается в начало функции и до присваивания значение undefined. let и const имеют блочную область, {}. Обратиться к ним до строки объявления нельзя: промежуток от начала блока до инициализации называется temporal dead zone, доступ бросает ReferenceError.
const запрещает переприсвоить связывание. Содержимое объекта при этом менять можно: константа держит ссылку, не глубокую заморозку.
Функциональное объявление function foo() {} всплывает целиком, её можно вызвать выше по коду функции. const foo = () => {} всплывает как let: до строки инициализации вызова нет.
Разбор
Цикл с var и setTimeout печатает одно и то же конечное значение: на все итерации одна переменная. let в заголовке for создаёт связывание на итерацию, колбэк видит своё i.
Блоки if и for для let — отдельные области. Для var они прозрачны, переменная видна во всей функции. В строгом режиме повторное let в том же блоке — ошибка, повторный var молча сливается.
Модуль ES сам по себе в строгом режиме. Переменная модуля не становится свойством window. Скрипт без type="module" и без 'use strict' ещё может положить var в глобальный объект.
На что смотрят на senior
Hoisting — про порядок связывания имён при создании окружения, не про то, что движок физически переставляет строки. Инициализация let остаётся на своей строке, поэтому TDZ и существует.
Замыкание держит переменную, не значение на момент создания функции. Это стык с темой замыканий: колбэк, созданный в цикле, читает переменную позже.
Типичные ошибки
- Считать, что
constделает объект неизменяемым. - Объяснять
varблочной областью. - Думать, что стрелочная функция всплывает так же, как
function declaration. - Ловить TDZ через
try/catchвокруг обычного кода вместо переноса объявления выше.
Задача
Что напечатает цикл и почему второй console.log упадёт?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0)
}
{
console.log(n)
let n = 1
}Показать ответ
Таймеры увидят одну переменную i. К моменту их запуска цикл уже закончился, три раза выведется 3.
let n внутри блока в зоне temporal dead zone до своей строки. console.log(n) бросит ReferenceError, а не напечатает undefined.