Всплывают ли переменные, объявленные с помощью let и const в ES6?

Grundy

Проверяя новшества ES6, я обратил внимание, что переменные объявленные с var всплывают как и ожидалось...

console.log(typeof name); // undefined
var name = "John";

...в то время как, переменные объявленные с помощью let или const похоже имеют с этим какие-то проблемы:

console.log(typeof name); // ReferenceError
let name = "John";

и

console.log(typeof name); // ReferenceError
const name = "John";

Означает ли это, что переменные объявленные с помощью let или const не всплывают? Что происходит здесь на самом деле? Есть ли разница между let и const в этом случае?

Перевод вопроса: Are variables declared with let or const not hoisted in ES6?

1 ответ

Grundy

tl; dr; цитата из секции 13.3.1 Let and Const Declarations

The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated.

Переменные создаются когда инстанциируется содержащий их Lexical Environment, но остаются недоступными никоим образом до вычисления их LexicalBinding.

Так что, ответ на твой вопрос: да, let и const всплывают, но ты не можешь получить доступ к ним, до выполнения фактического определения в run-time.

Как сказано выше, эти переменные не могут быть доступны, до их объявления. Однако, все немного сложнее.

Означает ли это, что переменные объявленные с помощью let или const не всплывают? Что происходит здесь на самом деле?

В JavaScript все определения (var, let, const, function, function*, class) всплывают. Это значит, что если имя определено в скопе, в этом скопе идентификатор всегда будет указывать на конкретную переменную:

x = "global";
// функциональный скоп:
(function() {
    x; // не "global"

    var/let/… x;
}());
// блочный скоп (не для `var`):
{
    x; // не "global"

    let/const/… x;
}

Это верно и для функционального, и для блочного скопов1.

Различие между определениями var/function/function* и let/const/class в их инициализации. Первые инициализируются значением undefined или функцией (генератором) прямо когда создается связывание в начале скопа. Однако, лексически объявленные переменные остаются неинициализированными. Это значит, что при попытке доступа к ним будет брошено исключение ReferenceError. Инициализируются они. только в момент выполнения выражений let/const/class, все перед этим (выше) называется временная мертвая зона (temporal dead zone).

x = y = "global";
(function() {
    x; // undefined
    y; // Reference error: y is not defined

    var x = "local";
    let y = "local";
}());

Обратите внимание, что выражение let y; инициализирует переменную значением undefined, аналогично выражению let y = undefined;.

Временная мертвая зона, это не синтаксическое место, а, скорее, время между созданием переменной (скопа) и инициализацией. Так, не будет ошибкой обращение к переменной в коде выше определения, пока этот код не будет выполнен (например тело функции или просто недостижимый код), но будет выкинуто исключение пи попытке доступа к переменной до ее инициализации, даже если код доступа расположен ниже определения (например, в всплывшем определении функции, которая была вызвана слишком рано).

Есть ли разница между let и const в этом случае?

Нет, они работают абсолютно одинаково, если рассматривать со стороны всплытия. Единственное отличие их в том, что constанты должны и могут быть присвоены только в части инициализатора определения (const one = 1;, вариант const one; с последующим присвоением значения one = 2 недопустим).

1: естественно, определение var работает только на уровне функции

Переводы ответов @thefourtheye и @Bergi

licensed under cc by-sa 3.0 with attribution.