Redefining a let variable inside a for loop scope doesn't work?

# /#!/JoePea (8 years ago)

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 using let, 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 variable n inside the scope.

Why is this not the case? Is it by design, and if so why?

# Blake Regalia (8 years ago)

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.

# Logan Smyth (8 years ago)

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.

# Allen Wirfs-Brock (8 years ago)

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.

# J Decker (8 years ago)

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, 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.

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++?

# /#!/JoePea (8 years ago)

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!