Всплывают ли переменные, объявленные с помощью 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 <span>may not be accessed in any way until the variable's LexicalBinding is evaluated.</span>

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

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

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

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

В 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;.

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

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

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

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

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

licensed under cc by-sa 3.0 with attribution.