3

When I do something (stupid) like this:

function doBar(bar) {
  for (let bar = 0; bar < 10; bar++) {
    let bar = true;
  }
}

Babel generates this:

function doBar(bar) {
  for (var bar = 0; bar < 10; bar++) {
    var _bar = true;
  }
  // bar is redeclared and has the value 10
}

I used the babeljs.io tool with the es2015 option.

Please check the babeljs.io example


Question: Should it work that way?

Why is the generated javascript not something like this:

for (var _bar = 0; _bar < 10; _bar++) {
  var _bar = true;
}

or

for (var _bar = 0; _bar < 10; _bar++) {
  var _bar2 = true;
}


Edit: Added some console output to the babeljs.io example to show how the function variabel is redeclared.
WebCore IT
  • 165
  • 1
  • 7

2 Answers2

1

Babel is only partially right here.

When I do something (stupid) like this:

function doBar(bar) {
  for (let bar = 0; bar < 10; bar++) {
    let bar = true;
  }
}
doBar("BAR");

…then you probably did not expect that you are having three different variables named bar in three different scopes here.

But it's true:

  1. The function-scope (var-like) bar declared by the parameter
  2. The loop-scoped bar that is introduced by the let declaration in the foo header, with its special scoping rules
  3. The block-scoped bar that is introduced by the let declaration in the block that forms the loop body statement

They all do have separate values (1: string, 2: integer, 3: boolean) and do not affect each other. This is mostly apparent because no syntax error is thrown - otherwise any let redefining a function-scoped variable (parameter, var declaration) or a let in the same scope as itself will throw an early error.

Babel generates this […]. Should it work that way?

No. As you can see in doFoo, Babel actually recognises that the variables 1 and 2 are different. It also does recognise that 2 and 3 are different. But as soon as you introduce all three, it confuses 1 and 2, transpiling them both to the same identifier.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
-1

let and var are not the same.

let defines a variable in a block scope, such as your for loop. var defines a variable in the global or function scope.

So when you get the code:

function doBar(bar) {
  for (let bar = 0; bar < 10; bar++) {
    let bar = true;
  }
}

the outside "bar" is defined for the function doBar while the inner "bar" is defined for the for(){} block scope.

There is no "redeclaring". When bable converts this, you get the expected behavior, the outside "bar" is within function, when you exit the for loop it remains at the max value, the inside _bar is an entirely different variable.

Since they are both converted to var instead of let and are hoisted.

Comment related example:
If you were to have this type of code:

function doBar(bar) {
  for (let bar = 0; bar < 10; bar++) {
    let bar = true;
  }
}

To begin with this is semantically wrong. This contains a re-declaration. Both of these bar variables are declared within the scope of the for's block. However you will get no error thrown, and this is the case without babel as well. Since the re-declaration of the variable bar occurs within the for's scope block, the for's "parameter" bar is overridden and inaccessible to the block itself, however it seems that the loop still runs normally, the correct number of times.

Because of this "silent error" and re-declared variable let bar within the block, babel doesn't see these two variables as the same, they are different to it. The reason for the error lies in the fact that by re-declaring a variable within the block, you effectively make the block's own variable in-accessible to anything, either the block or outside of it.

Now look at it from babel's perspective, the loop should run, the loop's parameter is not accessible anywhere inside or outside of the for's parenthesis, thus this is an isolated "scope" for the bar within the for.

Unlike let, var can be re-declared without raising an error, however this silent re-declaration doesn't raise an error in both cases, where one should have been risen and that is what causes the issue.

The solution is to write a proper code, but you are 100% right, this is not the correct behavior, however, neither is that of the regular javascript interpreter of the non-bable code as it should throw an error, and you cannot expect/blame babel for interpreting wrong code the wrong way.

Dellirium
  • 1,362
  • 16
  • 30
  • _"When bable converts this, you get the expected behavior"_ No, you don't. The parameter `bar` must remain unaltered. – a better oliver Jun 16 '16 at 10:57
  • And it does. but you are accessing the variable bar, not the parameter – Dellirium Jun 16 '16 at 13:02
  • I know of the different scopes of `var` and `let`. In fact that's the reason why I ask. :) I thought everything in the `for(){}` is scoped there. Please check the babeljs.io example (sorry, link is in the question and to large for the comments). Please see line 18 on the right side. When I access the variable `bar` here, I expect to get the parameter of the function - like in the `doFoo` example in line 9. What is the 'rule' for the different versions and the different scoping? – WebCore IT Jun 16 '16 at 13:24
  • 1
    Insert a `console.log(bar)` after the `for` loop. In a fully ES2015 compliant environment it prints the value of the parameter. The transpiled code prints `10`. You cannot declare a variable named `bar` using `let` in the function's scope. It is possible using `var`. Then, you are right, the parameter is shadowed by the variable. The transpiled code is incorrect nonetheless. – a better oliver Jun 16 '16 at 13:31
  • It is an incorrect code, no doubt, but the reasoning for it lies in the conversion of let to var, regardless of what happens inside the for() loop. If this is not the point of OP's question then I missed the point. Explaining further: – Dellirium Jun 16 '16 at 14:30
  • I fiddled around with the example linked and here is my conclusion: babel doesn't count variables declared with let under the parenthesis of the for block as if they were declared, else it would raise an error. They are still accessible, however you are overriding the declaration of "bar", or rather declaring it twice thus the "outside" bar, which is what we have running the loop is never actually used, and I believe this is why the converter doesn't change it. – Dellirium Jun 16 '16 at 14:40
  • Updating the post to continue this example – Dellirium Jun 16 '16 at 14:40