Redefining a let variable inside a for loop scope doesn't work?
let n of n.a
is inside the function
scope, not the for
scope (look at
the brackets). This is invalid/terrible-practice because n
is already
defined, and you are trying to use let
to declare a new variable even
though it already exists in the same scope.
I think you may be misunderstanding that error. for (let n of foo){ }
does create an n
value inside the loop. The issue isn't that n
doesn't
exist inside the loop, it's that n
has already been shadowed by the time
n.a
is evaluated, meaning you're accessing .a
of an uninitialized
binding.
In a little bit of a handwavy example, you could think of
let n = {a:[1,2,3]}
for (let n of n.a) {
console.log(n);
}
expanding out kind of like this:
let n = {a:[1,2,3]};
let _iterable;
{
_iterable = n.a;
// Shadow 'n' so the line above here will always error out because 'n' is
uninitialized.
let n;
}
for (var _value of _iterable) {
let n = _value;
console.log(n);
}
So the let n;
shadows the outer let n = {a: [1,2,3]};
when evaluating
n.a
, resulting in a TDZ error.
You can see this behavior of the for
head defined in
www.ecma-international.org/ecma-262/7.0/#sec-runtime-semantics-forin-div-ofheadevaluation-tdznames-expr-iterationkind,
where the usage of let
in the head causes step 2 of loop head evaluation
to create a special new environment that specifically exists during the
execution of the n.a
expression.
This is by design. ECMAScript block scoped declarations generally conform to the principle that within a single block or statement a name may have only a single binding. For example,
let x=0;
{
let x = x+1; //access to uninitialized variable error because both the RHS and LHS refer to the inner x
//which has not yet been initialized when the RHS is evaluated
}
The same principle also applies to
let n = {a:[]};
for (let n of n.a) ;
although the actual scoping of the for
statement is more complex. Basically, such for
statements consider all names bound by the let
part of of the for
header to be undefined while the of
expression is being evaluated.
This is done to avoid any confusion about which n
the expression references. Such confusion is avoid by all references to the let
bound names from the RHS errors.
On Fri, Jul 15, 2016 at 7:18 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>
wrote:
This is by design. ECMAScript block scoped declarations generally conform to the principle that within a single block or statement a name may have only a single binding. For example,
let x=0; { let x = x+1; //access to uninitialized variable error because both the RHS and LHS refer to the inner x //which has not yet been initialized when the RHS is evaluated }
The same principle also applies to
let n = {a:[]}; for (let n of n.a) ;
although the actual scoping of the
for
statement is more complex. Basically, suchfor
statements consider all names bound by thelet
part of of thefor
header to be undefined while theof
expression is being evaluated.
Wouldn't that scope for the 'for' loop declaration allow testing the 'n' variable after the loop? (test to see if it was broken out of completed), without requiring extra braces around the for? (Although sounds like just dong
{ for( let n ...) { } // can test/use N here? }
would also have worked like the workaround for C#/C++?
In a little bit of a handwavy example
I imagined a waving hand, holding a piece of chalk with a chalkboard in the background. Thanks for explaining!
The following examples are very confusing, and throw an error that
n
is undefined:function go(n){ for (let n of n.a) { console.log(n); } } go({a:[1,2,3]});
let n = {a:[1,2,3]} for (let n of n.a) { console.log(n); }
Why does the
let n
in the for loop not create a new variable inside the for loop scope? This seems to go against the intuitive expectation of usinglet
, which is to make variable scoping more clear and less error-prone, so it seems that one would expect the above examples to create a new variablen
inside the scope.Why is this not the case? Is it by design, and if so why?