JavaScript closure inside loops – simple practical

2020-01-22 07:43发布

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));
}

What’s the solution to this basic problem?

30条回答
\"骚年 ilove
2楼-- · 2020-01-22 07:59

After reading through various solutions, I'd like to add that the reason those solutions work is to rely on the concept of scope chain. It's the way JavaScript resolve a variable during execution.

  • Each function definition forms a scope consisting of all the local variables declared by var and its arguments.
  • If we have inner function defined inside another (outer) function, this forms a chain, and will be used during execution
  • When a function gets executed, the runtime evaluates variables by searching the scope chain. If a variable can be found in a certain point of the chain it will stop searching and use it, otherwise it continues until the global scope reached which belongs to window.

In the initial code:

funcs = {};
for (var i = 0; i < 3; i++) {         
  funcs[i] = function inner() {        // function inner's scope contains nothing
    console.log("My value: " + i);    
  };
}
console.log(window.i)                  // test value 'i', print 3

When funcs gets executed, the scope chain will be function inner -> global. Since the variable i cannot be found in function inner (neither declared using var nor passed as arguments), it continues to search, until the value of i is eventually found in the global scope which is window.i.

By wrapping it in an outer function either explicitly define a helper function like harto did or use an anonymous function like Bjorn did:

funcs = {};
function outer(i) {              // function outer's scope contains 'i'
  return function inner() {      // function inner, closure created
   console.log("My value: " + i);
  };
}
for (var i = 0; i < 3; i++) {
  funcs[i] = outer(i);
}
console.log(window.i)          // print 3 still

When funcs gets executed, now the scope chain will be function inner -> function outer. This time i can be found in the outer function's scope which is executed 3 times in the for loop, each time has value i bound correctly. It won't use the value of window.i when inner executed.

More detail can be found here
It includes the common mistake in creating closure in the loop as what we have here, as well as why we need closure and the performance consideration.

查看更多
来,给爷笑一个
3楼-- · 2020-01-22 08:00

try this shorter one

  • no array

  • no extra for loop


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

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

http://jsfiddle.net/7P6EN/

查看更多
ら.Afraid
4楼-- · 2020-01-22 08:00

Your code doesn't work, because what it does is:

Create variable `funcs` and assign it an empty array;  
Loop from 0 up until it is less than 3 and assign it to variable `i`;
    Push to variable `funcs` next function:  
        // Only push (save), but don't execute
        **Write to console current value of variable `i`;**

// First loop has ended, i = 3;

Loop from 0 up until it is less than 3 and assign it to variable `j`;
    Call `j`-th function from variable `funcs`:  
        **Write to console current value of variable `i`;**  
        // Ask yourself NOW! What is the value of i?

Now the question is, what is the value of variable i when the function is called? Because the first loop is created with the condition of i < 3, it stops immediately when the condition is false, so it is i = 3.

You need to understand that, in time when your functions are created, none of their code is executed, it is only saved for later. And so when they are called later, the interpreter executes them and asks: "What is the current value of i?"

So, your goal is to first save the value of i to function and only after that save the function to funcs. This could be done for example this way:

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

This way, each function will have it's own variable x and we set this x to the value of i in each iteration.

This is only one of the multiple ways to solve this problem.

查看更多
聊天终结者
5楼-- · 2020-01-22 08:03

Use closure structure, this would reduce your extra for loop. You can do it in a single for loop:

var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}
查看更多
成全新的幸福
6楼-- · 2020-01-22 08:04

Here's another variation on the technique, similar to Bjorn's (apphacker), which lets you assign the variable value inside the function rather than passing it as a parameter, which might be clearer sometimes:

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

Note that whatever technique you use, the index variable becomes a sort of static variable, bound to the returned copy of the inner function. I.e., changes to its value are preserved between calls. It can be very handy.

查看更多
Melony?
7楼-- · 2020-01-22 08:05

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.

查看更多
登录 后发表回答