Closures in a for loop and lexical environment

2019-01-09 14:41发布

Simple case: I want to load several images which have a common name and a suffix, e.g: image0.png, image1.png, image2.png ... imageN.png

I'm using a simple for loop:

var images = [];
for (var i=1; i<N; i++) {
    images[i] = new Image();
    images[i].onload = function () {
        console.log("Image " + i + " loaded");
    };
    images[i].src = "image" + i + ".png";
}

What I'm getting in the Console is:

Image N loaded
Image N loaded
Image N loaded
...
Image N loaded

But what I want should be like this:

Image 0 loaded
Image 1 loaded
Image 2 loaded
...
Image N loaded

Why is this happening? How can I get my desired behavior?

4条回答
贪生不怕死
2楼-- · 2019-01-09 14:46

Your loop counter variable has been overwritten already. Check out this FAQ entry explaining exactly why it happens and how to work around the issue.

查看更多
女痞
3楼-- · 2019-01-09 14:50

You can wrap it in a closure to avoid to use i variable, which is a loop variable and thus changes:

(function(j) {
  images[i].onload = function () {
      console.log("Image " + i + ", " + j + " loaded");
  };
})(i);

This demonstrates the difference between i, which is a loop variable and changes, and j, which is a function-bound parameter, which doesn't change.

See the jsfiddle here:

查看更多
We Are One
4楼-- · 2019-01-09 14:55

Since the variable i is declared outside the scope of the loop, it retains its final value after the loop has completed. The anonymous functions you're creating then all bind to this variable, and when they're called, they all get the same final value of N.

There's a good discussion of this in this question.

查看更多
Deceive 欺骗
5楼-- · 2019-01-09 15:10

The i inside your function is evaluated when the function is executed, not when you assign it to onload. Your for loop has already completed by the time any of your onload functions fire, so all of them see the final value N.

To capture the current value of i, you need to pass it as a parameter to another function where it can be captured as a local variable:

function captureI(i) {
    return function () {
        console.log("Image " + i + " loaded");
    };
}

var images = [];
for (var i=1; i<N; i++) {
    images[i] = new Image();
    images[i].onload = captureI(i);
    images[i].src = "image" + i + ".png";
}

This works because every time you call captureI, a new local variable is created for that instance of captureI. In essence, you are creating N different variables and each onload function captures a different instance of the variable.

查看更多
登录 后发表回答