-2

Say I have this function:

function printFruits(fruits) {
   for (var i = 0; i < fruits.length; i++) {
       setTimeout( function() {
          console.log( fruits[i]);
       }, i * 1000);
   }
}

printFruits(["Lemon", "Orange", "Mango"])

So this returns undefined 3 times.

I can see on a high level that since variables are stored not by value but by reference inside the closure... the loop is finishing first and by the time the functions are dequeued from maybe the Event Loop... the variable is already at undefined (fruits.length evaluates to 3 which is too high for this array size). But why does this perform strangely... it prints "apple" 3 times.

function printFruits(fruits) {
       for (var i = 0; i < fruits.length; i++) {
           var someConstant = i;
           setTimeout( function() {
              console.log( fruits[someConstant]);
           }, someConstant * 100);
       }
    }

printFruits(["mango", "banana", "apple"])

Shouldn't someConstant be changing as well with i? Why does it seem to be 2 always?

Also this works:

function printFruits(fruits) {
       for (var i = 0; i < fruits.length; i++) {
         (function() {
            var current = i;
              setTimeout( function() {
                 console.log( fruits[current]);
              }, current * 1000);
          })();
       }
    }

Why is the IIFE necessary to fix this problem?

Jwan622
  • 11,015
  • 21
  • 88
  • 181
  • 1
    "Shouldn't someConstant be changing as well with " — That's going to throw a reference errer because you never define `someCosntant` (spelling!). If I fix that error … it doesn't work: http://jsbin.com/yegimi/1/edit?js,console Try creating real [mcve]s. Preferably with [live demos](https://stackoverflow.blog/2014/09/introducing-runnable-javascript-css-and-html-code-snippets/). – Quentin Dec 15 '16 at 16:49
  • 2
    Define "works". Doesn't that code display `Mango` three times…? – deceze Dec 15 '16 at 16:51
  • `someConstant` and `i` are both defined in the same scope. `someConstant` is just an alias for `i`. As such it has the same fundamental problem, even if the result is slightly different. This whole thing is a problem of *scope*. Only creating a new scope can fix the problem. Adding more variables to the same scope won't. – Felix Kling Dec 15 '16 at 16:57
  • Edited the question to be correct. – Jwan622 Dec 16 '16 at 15:23

3 Answers3

3

2nd Example

function printFruits(fruits) {
  for (var i = 0; i < fruits.length; i++) {
    var someConstant = i;
    setTimeout(function() {
      console.log(fruits[someConstant]);
    }, someConstant * 1000);
  }
}
printFruits(["Lemon", "Orange", "Mango"])

This logs thrice Mango. Because everytime the someConstant variable is created and re-initialised to i. Recollect how for loop works. i-value is increasing here till 4, checks the condition 4<3, and terminates. So the matter inside the loop executes only thrice. So the last value of someConstant defined in the printFruits functional scope is 2. So when the inner function executes someConstant, its equal to 2. So we get each time Mango.

3rd example

function printFruits(fruits) {
  for (var i = 0; i < fruits.length; i++) {
    (function() {
      var current = i;
      setTimeout(function() {
        console.log(fruits[current]);
      }, current * 1000);
    })();
  }
}

printFruits(["Lemon", "Orange", "Mango"])

Here the beauty of closures happening. Here its a self executing function being executed, immediately. So when i = 1, it invokes immediately. Every function has a different scope now. There is a seperate current value defined for each. So later when it executes, it recollects whats the value of 'current' when it was defined inside its scope.

Ayan
  • 2,300
  • 1
  • 13
  • 28
  • *"Because everytime the someConstant variable is created and re-initialised to i."* That's not quite right. There is only a single `someConstant` variable during the whole lifetime of the program and it is created before the code is even executed. What happens is that `someConstant` simply gets assigned the current value of `i`. – Felix Kling Dec 15 '16 at 17:17
  • @FelixKling Hi, but there is a `var someConstant`. *simply gets assigned the current value * is something that happens when we var devlare once and assigns, Should there be multiple variable creation with same name? Say for first var statement, there is a memory allocation. Next time when we do again var, what happens? Does it overwrite the allocation, and simply re-assings the value? I mean how different would the stuff be if there was no var being used. – Ayan Dec 15 '16 at 17:25
  • All `var` declarations are processed before any code is executed. So `var foo = 42;` is basically the same as `var foo; /* other code */ foo = 42;`. The declaration happens *once* at the beginning of the execution. Only a single variable is ever created. – Felix Kling Dec 15 '16 at 17:35
2

The only difference between these samples is that the for loop increments i to 3 before stopping, while the last value which is assigned to someConstant is 2. Your "working" code is outputting Mango three times (index 2 of the array) instead of undefined (index 3 of the array). The general behaviour is the same.

Yes, you do need an IIFE, or ES6's let keyword instead of var.

deceze
  • 510,633
  • 85
  • 743
  • 889
1

The difference is that someConstant never gets incremented after the last iteration. The for() loop sets i = 3, the test i < fruits.length fails, so the loop stops. As a result, someConstant is still set to 2 from the last iteration of the loop. Then all the callbacks run, so they all log fruits[2], which is Mango.

You need the IIFE to get each iteration to save its value of i in the closure.

Barmar
  • 741,623
  • 53
  • 500
  • 612