lovebug356
lovebug356

Reputation: 473

var scope in javascript functions

I have the following small code snippet with the following expected and real output. My question is quiet simple. Why is it printing it this sequence? and how to I print the expected output?

Gr,

expected result:

0
1
2
0
1
2

real result:

0
1
2
3
3
3

this is the code:

var functions = [];

for (var i=0; i<10; i++) {
  console.log (i);
  functions.push (function () {
    console.log (i);
  });
};

for (var j=0; j<functions.length; j++) {
  functions[j] ();
};

Upvotes: 0

Views: 835

Answers (3)

Jeff Escalante
Jeff Escalante

Reputation: 3167

You should update your code example to be i < 3 so that your results and function match up.

The functions you push into the functions array are storing a reference to the variable i, which after executing the top loop, is 10. So when it executes, it will go get the variable i (which is 10) and print that 10 times.

Here's a good way to see this in action:

for (var i=0; i<10; i++) {
  console.log (i);
};

console.log(i) //=> 10

When you are using a variable, remember that the variable can change, it's not frozen at it's current value. You are only holding on to a reference to something else.

To fix this problem, I would run this type of minor refactor on the code (because the other answers have already created an extra scope, figured I'd give you something different). Rather than storing 10 functions, just store the numbers and execute them with a single function. This is a more elegant way to write it anyway, and takes up less space. I'm sure this example was abstracted from whatever code was really giving you the problem, but the general pattern still applies.

numbers = [];
for (var i=0; i<10; i++) {
  console.log (i);
  numbers.push(i);
};

numbers.forEach(function(i){
   console.log(i);
});

Upvotes: 0

Justin Summerlin
Justin Summerlin

Reputation: 5106

The expected result should be:

1
2
...
10
10
10
... 7 more times

The reason for this is simple. The console.log(i) inside your loop is correctly printing the value of i at each iteration of the loop. When you create and push a function into the functions array, what you're doing is closing each of those functions over the same variable i. At the end of your loop, i no longer satisfies your loop condition, so i = 10 is true. As a result, since each of those functions is going to execute console.log(i), and they're each closed over the same i, which now has value 10, you should expect to see the value 10 printed 10 times.

To prevent this, you will want to make a function which returns a function rather than creating functions directly in a loop:

var functions = [], i, j;
function createEmitter(i) {
  return function () {
    console.log(i);
  };
}

for (i = 0; i < 10; i++) {
  console.log(i);
  functions.push(createEmitter(i));
};

for (j = 0; j < functions.length; j++) {
  functions[j]();
};

Now, each of those created functions is closed over its own private scope variable, which resolves the problem.

Upvotes: 2

Guffa
Guffa

Reputation: 700910

The functions that you push into the array doesn't log the value of i as it was when the function was created, they log the value of i at the time that the function is called.

Once the first loop ends, the value of i is 10, therefore any of the functions called after that will log the value 10.

If you want to preserve the value of i at different states, you can use a closure to make a copy of the value:

for (var i=0; i<10; i++) {
  console.log (i);

  (function(){
    var copy = i;

    functions.push (function () {
      console.log (copy);
    });

  })();

};

The local variable copy will get the value of i and retain the value. You can also pass the value as a parameter to the function:

for (var i=0; i<10; i++) {
  console.log (i);

  (function(copy){

    functions.push (function () {
      console.log (copy);
    });

  })(i);

};

Upvotes: 5

Related Questions