6

I've been trying to figure out how the temporal dead zone/parsing of let and const work. This is what it seemingly boils down to (based on documentation and various responses I received in previous questions [such as this and this], though this goes against some answers given disagreement). Is this summary correct?

At the top of the scope, the JS engine creates a binding (an association of the variable keyword and name, e.g., let foo;) at the top of the relevant scope, which is considered hoisting the variable, but if you try to access the variable before the location of its declaration, JS throws a ReferenceError.

Once the JS engine moves down to the declaration (synonymous with "definition"), e.g., let foo;, the engine initializes it (allocating memory for it and making it accessible). The declaration is self-binding. (Here's the part that doesn't make sense to me: the binding is causing the hoisting at the top, but the engine doesn't initialize until it reaches the declaration, which also has a binding effect.) If there isn't an assignment, the value of the variable is set to undefined in the case of let or, if const is used, a SyntaxError will be thrown.

For reference here's what the specs say about it:

ECMAScript 2019 Language Specification draft: section 13.3.1, Let and Const Declarations

let and const declarations define variables that are scoped to the running execution context's LexicalEnvironment. 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. 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.

MDN Web Docs: Let

let bindings are created at the top of the (block) scope containing the declaration, commonly referred to as "hoisting". Unlike variables declared with var, which will start with the value undefined, let variables are not initialized until their definition is evaluated. Accessing the variable before the initialization results in a ReferenceError. The variable is in a "temporal dead zone" from the start of the block until the initialization is processed.

Deja
  • 3,278
  • 2
  • 18
  • 46
  • I'm not sure if the binding happens twice. From what you shared the binding is at the point of declaration which is hoisted. That's just 1 binding of the variable. – apokryfos Jun 18 '18 at 07:50

2 Answers2

0

TDZ is quite complex to understand and requires a blog post to clarify how that actually works. But in essence, overly simplified explanation is

let/const declarations do hoist, but they throw errors when accessed before being initialized (instead of returning undefined as var would)

Let's take this example

let x = 'outer scope';
(function() {
    console.log(x);
    let x = 'inner scope';
}());

the code above will throw a ReferenceError due to the TDZ semantics.

All these are from this great article completely about TDZ. Kudos for the author.

code-jaff
  • 8,472
  • 4
  • 30
  • 52
0

Maybe first you need to understand why the TDZ exists: because it prevents common surprising behaviour of variable hoisting and fixes a potential source of bugs. E.g.:

var foo = 'bar';

(function () {
    console.log(foo);
    var foo = 'baz';
})();

This is a frequent cause of surprise for many (novice) programmers. It's too late to change the behaviour of var now, so the ECMAScript group decided to at least fix the behaviour together with the introduction of let and const. How exactly it is implemented under the hood is a somewhat moot point, the important thing is that it stops what it most likely a typo/structural mistake dead in its tracks:

let foo = 'bar';

(function () {
    console.log(foo);
    let foo = 'baz';
})();

Practically speaking Javascript is executed in a two-step process:

  1. Parsing of the code into an AST/executable byte code.
  2. Runtime execution.

The parser will see var/let/const declarations in this first step and will set up scopes with reserved symbol names and such. That is hoisting. In the second step the code will act in that set up scope. It should be somewhat obvious that the parser/engine is free to do whatever it wants in that first step, and one of the things it does is to flag the TDZ internally, which will raise an error at runtime.

deceze
  • 471,072
  • 76
  • 664
  • 811