The problem is not really that x
gets declared twice. It is just that you are trying to access the inner x
before it got initialized:
let x = x /* doesn't exist yet*/;
Wether there is another x
in the outer scope (initializers in a for
loop are inside their own scope) doesn't matter, the x
will refer to the variable in the current scope, as it already got declared (due to hoisting), but wasn't initialized yet:
let x = 0; // irrelevant
{ // x gets declared as part of this scope
x; // thats an error too as x is not initialized yet
let x = 1; // initialization
x; // now it can be accessed
}
The part between the beginning of a scope and a let
declaration is called the "temporal dead zone" ...
"... in a for loop, the initializer expression is evaluated outside the scope of the new variable"
No, otherwise you couldn't refer to other variables in the initializer:
for(let a = 1, b = a; ; )
As always, the definite answer can be found in the spec:
13.7.4.7 Runtime Semantics: LabelledEvaluation
IterationStatement : for ( LexicalDeclaration Expression ; Expression ) Statement
Let oldEnv be the running execution context's LexicalEnvironment.
Let loopEnv be NewDeclarativeEnvironment(oldEnv).
[...]
Let boundNames be the BoundNames of LexicalDeclaration.
For each element dn of boundNames [..]
Perform ! loopEnvRec.CreateImmutableBinding(dn, true).
Set the running execution context's LexicalEnvironment to loopEnv.
Let forDcl be the result of evaluating LexicalDeclaration.
[...]
As you can see, the running execution context is loopEnv
while the LexicalDeclaration (the initializers) gets evaluated, not oldEnv
.
TLDR: Not only the example is wrong, but also the paragraph.