Search code examples
javascriptvariableslethoisting

ReferenceError: can't access lexical declaration`X' before initialization


Can someone explain me the weird error messages that occur both in chrome and firefox when trying to access a variable before the let declaration: "let X". if we write something like:

console.log(X);
let X;

/*
In firefox it reports an error like:

ReferenceError: can't access lexical declaration 'X' before initialization


In chrome it reports and error like:

Uncaught ReferenceError: Cannot access 'X' before initialization

*/

why it returns an error message like you saw in the above code without giving an error message like the below ones:

console.log(X);
/* the error message i was expecting:

firefox: ReferenceError: X is not defined

chrome: ReferenceError: X is not defined


*/

does that mean let variables hoist too because the error messages show attributes of let variables being hoisted means that the javaScript engine knows when we try to access a let variable before it's declared

console.log(X);
let X;
ReferenceError: can't access lexical declaration 'X' before initialization

and please if you find any thing related to this behavior in ECMAScript specification lemme know


Solution

  • Yes, they "hoist" but as unaccessible thing which always throws an error when read or written. It's called the "temporal dead zone".

    Further reading: https://medium.com/nmc-techblog/advanced-javascript-es6-temporal-dead-zone-default-parameters-and-let-vs-var-deep-dive-ca588fcde21b

    Conceptually, for example, a strict definition of hoisting suggests that variable and function declarations are physically moved to the top of your code, but this is not in fact what happens. Instead, the variable and function declarations are put into memory during the compile phase, but stay exactly where you typed them in your code. [Hositing]

    [...]

    For most of the ES6 features (let, const, default parameters, etc), The Creation Phase work quite differently. It still goes through the code and allocates space for variables, but the initializer set the variables with a special mode called TDZ (Temporal Dead Zone), which means the variables exist but you can’t access them until you assign some value.

    So you can imagine it like this:

    let X = TDZ;
    console.log(X); // error
    X = undefined; // in your code: let X;
    

    ...compared to normal hosting behavior:

    var X = undefined;
    console.log(X); // undefined
    X = whatever; // in your code: var X = whatever;
    

    Of course this is not 100% right because you also can't write X = 123 before the let, and there is no valid JS that would describe an "unwritable variable". But I think you get the idea.


    In the ECMAScript 2021 Language Specification this is described in 13.3.1 as follows (it seems it doesn't use the term "TDZ" there, although I have heard this name used many timed before, it's also used in MDN):

    13.3.1 Let and Const Declarations

    NOTE

    let and const declarations define variables that are scoped to the running execution context's LexicalEnvironment. The variables are created when their containing Environment Record is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer's AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.

    This means that at the start of your block, the Environment Record is instantiated - the variable "exists" but is in the TDZ. As said here, it cannot be accessed, hence the error. Once the let line is executed, its LexicalBinding is evaluated and the variable comes out of the TDZ and is now accessible. Since you didn't specify an initializer, its value is now undefined.