JavaScript closure inside loops – simple practical example

Viewed 458555

var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
  // and store them in funcs
  funcs[i] = function() {
    // each should log its value.
    console.log("My value: " + i);
  };
}
for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}

It outputs this:

My value: 3
My value: 3
My value: 3

Whereas I'd like it to output:

My value: 0
My value: 1
My value: 2


The same problem occurs when the delay in running the function is caused by using event listeners:

var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
  // as event listeners
  buttons[i].addEventListener("click", function() {
    // each should log its value.
    console.log("My value: " + i);
  });
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>

… or asynchronous code, e.g. using Promises:

// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));

for (var i = 0; i < 3; i++) {
  // Log `i` as soon as each promise resolves.
  wait(i * 100).then(() => console.log(i));
}

It is also apparent in for in and for of loops:

const arr = [1,2,3];
const fns = [];

for(var i in arr){
  fns.push(() => console.log(`index: ${i}`));
}

for(var v of arr){
  fns.push(() => console.log(`value: ${v}`));
}

for(var f of fns){
  f();
}

What’s the solution to this basic problem?

44 Answers

Well, the problem is that the variable i, within each of your anonymous functions, is bound to the same variable outside of the function.

ES6 solution: let

ECMAScript 6 (ES6) introduces new let and const keywords that are scoped differently than var-based variables. For example, in a loop with a let-based index, each iteration through the loop will have a new variable i with loop scope, so your code would work as you expect. There are many resources, but I'd recommend 2ality's block-scoping post as a great source of information.

for (let i = 0; i < 3; i++) {
  funcs[i] = function() {
    console.log("My value: " + i);
  };
}

Beware, though, that IE9-IE11 and Edge prior to Edge 14 support let but get the above wrong (they don't create a new i each time, so all the functions above would log 3 like they would if we used var). Edge 14 finally gets it right.


ES5.1 solution: forEach

With the relatively widespread availability of the Array.prototype.forEach function (in 2015), it's worth noting that in those situations involving iteration primarily over an array of values, .forEach() provides a clean, natural way to get a distinct closure for every iteration. That is, assuming you've got some sort of array containing values (DOM references, objects, whatever), and the problem arises of setting up callbacks specific to each element, you can do this:

var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
  // ... code code code for this one element
  someAsynchronousFunction(arrayElement, function() {
    arrayElement.doSomething();
  });
});

The idea is that each invocation of the callback function used with the .forEach loop will be its own closure. The parameter passed in to that handler is the array element specific to that particular step of the iteration. If it's used in an asynchronous callback, it won't collide with any of the other callbacks established at other steps of the iteration.

If you happen to be working in jQuery, the $.each() function gives you a similar capability.


Classic solution: Closures

What you want to do is bind the variable within each function to a separate, unchanging value outside of the function:

var funcs = [];

function createfunc(i) {
  return function() {
    console.log("My value: " + i);
  };
}

for (var i = 0; i < 3; i++) {
  funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}

Since there is no block scope in JavaScript - only function scope - by wrapping the function creation in a new function, you ensure that the value of "i" remains as you intended.

Try:

var funcs = [];
    
for (var i = 0; i < 3; i++) {
    funcs[i] = (function(index) {
        return function() {
            console.log("My value: " + index);
        };
    }(i));
}

for (var j = 0; j < 3; j++) {
    funcs[j]();
}

Edit (2014):

Personally I think @Aust's more recent answer about using .bind is the best way to do this kind of thing now. There's also lo-dash/underscore's _.partial when you don't need or want to mess with bind's thisArg.

Another way of saying it is that the i in your function is bound at the time of executing the function, not the time of creating the function.

When you create the closure, i is a reference to the variable defined in the outside scope, not a copy of it as it was when you created the closure. It will be evaluated at the time of execution.

Most of the other answers provide ways to work around by creating another variable that won't change the value for you.

Just thought I'd add an explanation for clarity. For a solution, personally, I'd go with Harto's since it is the most self-explanatory way of doing it from the answers here. Any of the code posted will work, but I'd opt for a closure factory over having to write a pile of comments to explain why I'm declaring a new variable(Freddy and 1800's) or have weird embedded closure syntax(apphacker).

What you need to understand is the scope of the variables in javascript is based on the function. This is an important difference than say c# where you have block scope, and just copying the variable to one inside the for will work.

Wrapping it in a function that evaluates returning the function like apphacker's answer will do the trick, as the variable now has the function scope.

There is also a let keyword instead of var, that would allow using the block scope rule. In that case defining a variable inside the for would do the trick. That said, the let keyword isn't a practical solution because of compatibility.

var funcs = {};

for (var i = 0; i < 3; i++) {
  let index = i; //add this
  funcs[i] = function() {
    console.log("My value: " + index); //change to the copy
  };
}

for (var j = 0; j < 3; j++) {
  funcs[j]();
}

This question really shows the history of JavaScript! Now we can avoid block scoping with arrow functions and handle loops directly from DOM nodes using Object methods.

const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())

const buttons = document.getElementsByTagName("button");
Object
  .keys(buttons)
  .map(i => buttons[i].addEventListener('click', () => console.log(i)));
<button>0</button><br>
<button>1</button><br>
<button>2</button>

We will check , what actually happens when you declare var and let one by one.

Case1 : using var

<script>
   var funcs = [];
   for (var i = 0; i < 3; i++) {
     funcs[i] = function () {
        debugger;
        console.log("My value: " + i);
     };
   }
   console.log(funcs);
</script>

Now open your chrome console window by pressing F12 and refresh the page. Expend every 3 functions inside the array.You will see an property called [[Scopes]].Expand that one. You will see one array object called "Global",expand that one. You will find a property 'i' declared into the object which having value 3.

enter image description here

enter image description here

Conclusion:

  1. When you declare a variable using 'var' outside a function ,it becomes global variable(you can check by typing i or window.i in console window.It will return 3).
  2. The anonymous function you declared will not call and check the value inside the function unless you invoke the functions.
  3. When you invoke the function , console.log("My value: " + i) takes the value from its Global object and display the result.

CASE2 : using let

Now replace the 'var' with 'let'

<script>
    var funcs = [];
    for (let i = 0; i < 3; i++) {
        funcs[i] = function () {
           debugger;
           console.log("My value: " + i);
        };
    }
    console.log(funcs);
</script>

Do the same thing, Go to the scopes . Now you will see two objects "Block" and "Global". Now expand Block object , you will see 'i' is defined there , and the strange thing is that , for every functions , the value if i is different (0 , 1, 2).

enter image description here

Conclusion:

When you declare variable using 'let' even outside the function but inside the loop , this variable will not be a Global variable , it will become a Block level variable which is only available for the same function only.That is the reason , we are getting value of i different for each function when we invoke the functions.

For more detail about how closer works , please go through the awesome video tutorial https://youtu.be/71AtaJpJHw0

The reason your original example did not work is that all the closures you created in the loop referenced the same frame. In effect, having 3 methods on one object with only a single i variable. They all printed out the same value.

Till ES5, This problem can only be solved using closure.

But now in ES6, we have block level scope variables. Changing var to let in first for loop will solve the problem.

var funcs = [];
for (let i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function() {          // and store them in funcs
    console.log("My value: " + i); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

If you're having this sort of problem with a while loop, rather than a for loop, for example:

var i = 0;
while (i < 5) {
  setTimeout(function() {
    console.log(i);
  }, i * 1000);
  i++;
}

The technique to close over the current value is a bit different. Declare a block-scoped variable with const inside the while block, and assign the current i to it. Then, wherever the variable is being used asynchronously, replace i with the new block-scoped variable:

var i = 0;
while (i < 5) {
  const thisIterationI = i;
  setTimeout(function() {
    console.log(thisIterationI);
  }, i * 1000);
  i++;
}

For older browsers that don't support block-scoped variables, you can use an IIFE called with i:

var i = 0;
while (i < 5) {
  (function(innerI) {
    setTimeout(function() {
      console.log(innerI);
    }, innerI * 1000);
  })(i);
  i++;
}

If the asynchronous action to be invoked happens to be setTimeout like the above, you can also call setTimeout with a third parameter to indicate the argument to call the passed function with:

var i = 0;
while (i < 5) {
  setTimeout(
    (thisIterationI) => { // Callback
      console.log(thisIterationI);
    },
    i * 1000, // Delay
    i // Gets passed to the callback; becomes thisIterationI
  );
  i++;
}

var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function(param) {          // and store them in funcs
    console.log("My value: " + param); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j](j);                      // and now let's run each one to see with j
}

Use let(blocked-scope) instead of var.

var funcs = [];
for (let i = 0; i < 3; i++) {      
  funcs[i] = function() {          
    console.log("My value: " + i); 
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      
}

Just change the var keyword to let.

var is function scoped.

let is block scoped.

When you start you code the for loop will iterate and assign the value of i to 3, which will remain 3 throughout your code. I suggest you to read more about scopes in node (let,var,const and others)

funcs = [];
for (let i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] =async function() {          // and store them in funcs
    await console.log("My value: " + i); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

This proves how ugly javascript is with regard to how 'closure' and 'non-closure' works.

In the case of:

var funcs = [];

for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function() {          // and store them in funcs
    console.log("My value: " + i); // each should log its value.
  };
}

funcs[i] is a global function, and 'console.log("My value: " + i);' is printing global variable i

In the case of

var funcs = [];

function createfunc(i) {
    return function() { console.log("My value: " + i); };
}

for (var i = 0; i < 3; i++) {
    funcs[i] = createfunc(i);
}

because of this twisted closure design of javascript, 'console.log("My value: " + i);' is printing the i from outer function 'createfunc(i)'

all because javascript can not design something decent like the 'static' variable inside a function like what C programming language is doing!

  asyncIterable = [1,2,3,4,5,6,7,8];

  (async function() {
       for await (let num of asyncIterable) {
         console.log(num);
       }
    })();

Why not simply call each function inside the first (and only) loop just after they were created, such as:

 var funcs = [];
    for (var i = 0; i < 3; i++) {
    // let's create 3 functions
    funcs[i] = function() {
    // and store them in funcs
    console.log("My value: " + i); // each should log its value.
    };
    funcs[i]();// and now let's run each one to see
    }

With the support of ES6, the best way to this is to use let and const keywords for this exact circumstance. So var variable get hoisted and with the end of loop the value of i gets updated for all the closures..., we can just use let to set a loop scope variable like this:

var funcs = [];
for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}

Already many valid answers to this question. Not many using a functional approach though. Here is an alternative solution using the forEach method, which works well with callbacks and closures:

let arr = [1,2,3];

let myFunc = (val, index) => { console.log('val: '+val+'\nindex: '+index); };

arr.forEach(myFunc);

Related