In ES6, do for loops with a let/const initializer create a separate scope?

# Michael Zhou (10 years ago)

In other words, is

for (let i = 0; i < 10; i++) {
   let i;
}

legal? I feel it is, but I'm not sure if the specs has made that clear.

# Allen Wirfs-Brock (10 years ago)

The above is legal according to the current ES6 spec. draft. The let in the loop body block shadows the per iteration binding of i.

You can think of the body of a let i for loops as desugaring into something like:

let $$lastIteration_i = <initialization expression for i>;
{
   let i = $$lastIteration_i;   //create and initialize per iteration i
   <test expression>;
   <loop body statement>
   <increment expression>;
   $$lastIteration_i = i;
}

For you sample, this expands to:

let $$lastIteration_i = 0;
{
   let i = $$lastIteration_i;   //create and initialize per iteration i
   if (!(i<10)) break;
  {
      let i;
   }
   i++;
   $$lastIteration_i = i;
}

So you can see that the let i in the block simply shadows the per iteration i.

The spec. doesn't say anything explicit about this case. It just falls out of the specified evaluation semantics of for loops.

We could consider special cases loop bodies that are BlockStatements and statically reject those that contain declaration that shadow the loop declarations. However, I think it is probably best to leave that sort of check for linters to perform.

# Andreas Rossberg (10 years ago)

On 13 June 2014 18:23, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

We could consider special cases loop bodies that are BlockStatements and statically reject those that contain declaration that shadow the loop declarations. However, I think it is probably best to leave that sort of check for linters to perform.

Yes. A block is a block, and block scoping has shadowing semantics. I see no point in making exceptions to this rule in a few random places. In fact, non-compositional scoping restrictions can be harmful, since they can get in the way of e.g. refactoring or JS as a compilation target.

# Michael Zhou (10 years ago)

Thanks for the clarification, one detail about the order between incrementing and setting $$lastIteration_i:

{
    let i = $$lastIteration_i;   //create and initialize per iteration i
    if (!(i<10)) break;
   {
       let i;
    }
*    i++;
    $$lastIteration_i = i;*
}

Should it be*

$$lastIteration_i = i;
i++;

instead, given what the spec says?

# Allen Wirfs-Brock (10 years ago)

neither of these desugarings is perfectly in alignment with the actual spec. which doesn't actually use a temp variable in this manner.

A closer match would be:

$$lastIteration_i = i;
$$lastIteration_i++;

The value of the new i for the next iteration is the incremented value of the previous i, but the value of the previous i is not incremented which is important for the cases where a closure has captured the previous i.