11

I was going through the Difference between var and let documentation example and was testing that when an undeclared variable is invoked, the global scope automatically provides a declaration for it (that's why the following snippet does not throw an error in any of the variables):

x = 3;
console.log(x);

(function() {
  y=x+39;
})()
console.log(y);

However, when one variable is declared with let after the assignment in the same global scope:

x=3;
let x = 42;
console.log(x);

One of the following errors is thrown:

ReferenceError: x is not defined (Chromium)

ReferenceError: can't access lexical declaration x before initialization (Firefox)

I understand that let does not allow x to hoist, but since it was previously referenced (implying an automatic declaration from the global scope) shouldn't in this case a re-declaration happen?

SyntaxError: Identifierx has already been declared

And therefore the error above thrown?

I also understand that in strict mode the first snippet would throw a ReferenceError, so does this mean that let forces this particular rule of strict mode (all variables need to be declared) upon the global scope?

Community
  • 1
  • 1
CPHPython
  • 12,379
  • 5
  • 59
  • 71
  • 1
    [`let` is hoisted as well](http://stackoverflow.com/q/31219420/1048572), you just are accessing it during the temporal dead zone. I don't see what strict mode has to do with this. – Bergi Jan 03 '17 at 19:51
  • @Bergi I was not fully aware of the [TDZ](http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified/), I was assuming that a non declared variable would be automatically declared in the global scope (at this time the [docs](http://stackoverflow.com/documentation/javascript/480/scope/1574/difference-between-var-and-let#t=201701031850353755045) only show a double `let` declaration). Since accessing a variable before its own `let` declaration throws a **ReferenceError**, I thought that a strict-mode-behavior/rule was related, but now I understand that it is due to `let` hoisting the variable. – CPHPython Jan 04 '17 at 11:47

3 Answers3

3

Did you have a look at the let docs at MDN? They describe a temporal dead zone and errors with let.

ES6 does hoist a let variable to the top of its scope. Differently to var variable, when using let you must not access the variable before it is declared. Doing so fail with a ReferenceError (a.k.a. let's temporal dead zone).

Konstantin A. Magg
  • 1,106
  • 9
  • 19
  • 1
    but isn't the x initialised to 3 which defaults to global declaration in the first statement. So, it should have a reference at statement #2 regardless of whether let hoists the variable or not. What are your views? – Lokesh Devnani Jan 03 '17 at 19:57
  • 1
    `let x;` is hoisted to the top of the scope - global scope in this case. The statement `x=3;` cannot create a new `var` as in this scope the name is already reserved for our `let`. It cannot access the variable `x` either as it is not declared yet. – Konstantin A. Magg Jan 03 '17 at 20:00
  • So, this is the kind of behaviour that prevents variables from using before declaration. This makes sense. However, I'll have to look more into it to be sure. Thanks – Lokesh Devnani Jan 03 '17 at 20:05
  • 1
    @KonstantinA.Magg your words in [your comment](http://stackoverflow.com/questions/41451181/does-let-override-a-global-declaration-and-throws-a-referenceerror/41452809#comment70109192_41451275) allowed me to understand it better than the words in your answer. Thanks. – CPHPython Jan 04 '17 at 11:50
2

You're right, it's weird behavior. The reason it's giving those errors is because it thinks you're trying to assign the value 3 to your let variable instead of the global value. As others mentioned, this leads to the temporal deadzone issue with hoisting.

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

- Source (ECMAScript 8th edition)

This code shows where placing code causes the TDZ:

// Accessing `x` here before control flow evaluates the `let x` statement
// would throw a ReferenceError due to TDZ.
// console.log(x);

let x = 42;
// From here on, accessing `x` is perfectly fine!
console.log(x);

You can see that wrapping the let inside its own block block fixes it:

x=3;
{
let x = 42;
console.log(x); // 42
}

Alternatively, you can define the global explicitly on the window object:

window.x=3;

let x = 42;
console.log(x);  // 42
Alex W
  • 37,233
  • 13
  • 109
  • 109
  • what about: ` var a = 1; function fn() { console.log(a); let a = 2; } fn(); ` –  May 31 '18 at 20:32
  • @AntonYaskevich Same thing, you are trying to log `x` before the statement is evaluated. – Alex W May 31 '18 at 20:52
0

As Konstantin A. Magg explained, that's because let variables are hoisted and attempts to reference them before initialization throw (temporal dead zone).

If you don't want this, you can split the code into different scripts:

<script>
x = 3;
console.log(x); // 3
</script>

<script>
let x = 42;
console.log(x); // 42
</script>

Note x = 3 will throw in strict mode.

Oriol
  • 274,082
  • 63
  • 437
  • 513