1

The following code throws a ReferenceError 'a is not defined'.

{
    let a = 'a1';
    {
        console.log(a);
        let a = 'a2';
    }
}

As do the next one.

{
    const a = 'a1';
    {
        console.log(a);
        const a = 'a2';
    }
}

If you instead use var declarations it works as I expect it to. (No error error thrown and 'a1' logged.)

It gets even harder for me to comprehend when I try to analyze the following code.

{
    let a = 'a1';
    {
        console.log(a);
        var a = 'a2';
    }
}

It throws a SyntaxError, "Identifier 'a' has already been declared".

Naïvely I expected an identifier to be shadowed not until after a let or const declaration, like the behavior of let in Clojure or let* in Racket. As described clearly on MDN, this is not how it works.

But why does it work this way? Why does Racket have both a let and a let* form?

Johan Jonasson
  • 500
  • 2
  • 18
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let – Teemu Jan 10 '17 at 13:03
  • Thanks for the link, but it does not answer my question. I understand _how_ it works, but not _why_ this model is chosen. Is it because of performance issues? – Johan Jonasson Jan 10 '17 at 13:09
  • 1
    SO is not a place to ask _why_ a language has a particular feature or behavior. Ask [TC-39](https://www.ecma-international.org/memento/TC39-RF-TG.htm), they know without speculations. I could speculate a bit. Maybe the hoisting mechanism was found so important, that it was preferred over the outer scope variables? – Teemu Jan 10 '17 at 13:12
  • 1
    @JohanJonasson Why it was chosen? Because scopes in ES6 are always block/function-body based, `let` does not introduce a new scope. This for example does allow mutually recursive declarations without extra hazzle. Also it's less error-prone not to have `a` available at all when you wouldn't be sure which variable it does refer to. – Bergi Jan 10 '17 at 15:23
  • Thanks @Bergi, that's an interesting answer. If I understand you correctly, every let/const declaration would have to introduce a new scope if they should behave the way I expected? – Johan Jonasson Jan 10 '17 at 15:39
  • Yes. If you rewrite your example to Clojure, the scopes become explicit. – Bergi Jan 10 '17 at 15:56

1 Answers1

1

This is caused by hoisting the internal let or const to the top of the block (MDN), and create a Temporal Dead Zone.

In ECMAScript 2015, let will hoist the variable to the top of the block. However, referencing the variable in the block before the variable declaration results in a ReferenceError. The variable is in a "temporal dead zone" from the start of the block until the declaration is processed.

The reason behind this is simple - silent failing vs throwing error.

In this example, a similar setting is giving the result as undefined because of the var hoisting. This is a silent error, and it might be very hard to debug.

{
    var a = 'a1';
    (function() {
        console.log(a);
        var a = 'a2';
    })();
}

If you'll use let or const an error will be thrown:

{
  let a = 'a1';
  (function() {
    console.log(a);
    let a = 'a2';
  })();
}

You can find more info about the temporal dead zone in the article TEMPORAL DEAD ZONE (TDZ) DEMYSTIFIED.

Ori Drori
  • 183,571
  • 29
  • 224
  • 209