2

When I executed this code I expected to see 100 written in the console 10 times (based on experience from using other languages):

const arr = [];
for (let i = 0; i < 10; i++)
    arr.push(() => i * i);

arr.forEach(f => console.log(f()));

But I get the square of each number (from 0 to 9). This seems to behave differently to some other languages.

For instance using C#, you will get 100 ten times:

    var list = new List<Func<int>>();

    for(var i = 0; i < 10; i++)
        list.Add(() => i * i);

    list.ForEach(f => Console.WriteLine(f()));

Python prints 81 ten times:

    arr = []
    for i in range(10):
        arr.append(lambda: i * i)

    for f in arr:
        print(f())

Java doesn't allow this, as captured variables cannot mutate.

See this for examples in other languages.

As Mark explained below, when we use let (block scope) in JavaScript, the value of i is captured in a new scoped variable within each anonymous function. When you look at the actual definition of each function (using : arr.forEach(f => console.dir(f)); ), you'll see that there is a scope (_loop) from which i's value is captured in every iteration:

  [[Scopes]]: Scopes[3]
  0: Closure (_loop) {i: 0}
  [[Scopes]]: Scopes[3]
  0: Closure (_loop) {i: 1}
   and so on...

If we re-run this example using var, unsurprisingly, the _loop block-level scope is missing and i is saved in the module/file level scope instead with the same value (10) for each iteration:

  [[Scopes]]: Scopes[2]
  0: Closure (./src/index.js) {i: 10}
Zoman
  • 2,047
  • 4
  • 28
  • 44
  • 2
    ‘i’ is not reference, how u expect 100 – Dennis Vash May 24 '19 at 20:35
  • @DennisVash I know it's not a reference, but my point was that it behaves differently from some other languages for instance try to run the same code in C#. – Zoman May 24 '19 at 20:46
  • @DennisVash this is not exactly an issue about primitive vs. reference values (see Mark's reply below), so please consider deleting or editing your comments to make sure others don't get misled. – Zoman May 25 '19 at 07:50
  • You use `let` in your JS, and `var` in your C++, and then wonder why they're different results? – Niet the Dark Absol May 25 '19 at 09:50
  • @NiettheDarkAbsol I did not provide an example in C++ and if you are referring to the C# example, it uses an implicitly typed local variable that is very different from a JavaScript var. – Zoman May 25 '19 at 09:55
  • C#, my mistake. But my point was, JS has `var` too that works exactly how it does in C# as far as closures go, apparently. – Niet the Dark Absol May 25 '19 at 09:57
  • @NiettheDarkAbsol I can assure you that the C# var has absolutely nothing to do with this. It is just syntactic sugar. See: https://stackoverflow.com/questions/4307467/what-does-var-mean-in-c – Zoman May 25 '19 at 10:01

1 Answers1

3

This is the nature of using let to declare a variable in javascript — it's scoped to the (implied here) for loop block, which means each iteration gets a newly scoped name that is captured in each closure. The closures aren't all using the same name like they would be in some other languages, or in JS when using var to declare a variable.

To get your expected behavior, use var:

const arr = [];
for (var i = 0; i < 10; i++)
    arr.push(() => i * i);

arr.forEach(f => console.log(f()));

The above code is the bane of many confused programmers which is one of the reasons it's nice to have the option of using let.

Mark
  • 90,562
  • 7
  • 108
  • 148