8

Please see the jsfiddle: http://jsfiddle.net/LsNCa/2/

function MyFunc() {

    for (var i = 0; i < 2; i++) { // i= 0, 1
        var myDiv = $('<div>');
        
        myDiv.click(function(e) {
            alert(i);    // both the two divs alert "2", not 0 and 1 as I expected
        });
        $('body').append(myDiv);
    }
}

var myFunc = new MyFunc();

I want the divs to alert "0" and "1" respectively when I click them, but both of them alert "2".

When I click the divs and the event is triggered, how and where do the handler find the value of the variable i?

I'm aware that adding a closure achieves my goal. But why?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Boyang
  • 2,520
  • 5
  • 31
  • 49
  • possible duplicate of [Javascript infamous Loop problem?](http://stackoverflow.com/questions/1451009/javascript-infamous-loop-problem) – elclanrs Jul 16 '13 at 07:42

4 Answers4

7
    function MyFunc() {

    for (var i = 0; i < 2; i++) { // i= 0, 1
        (function(j) {
            var myDiv = $('<div>');

            myDiv.click(function(e) {
                alert(j);
            });
            $('body').append(myDiv);
        })(i);
    }
}

var myFunc = new MyFunc();

The code above is how you get it work correctly. Without an closure, you always the the last value of i. What we do is to post i into the closure and let the runtime "remember" the value of that very moment.

yaoxing
  • 4,003
  • 2
  • 21
  • 30
  • Is this the standard way of doing it? If the loop body is fairly long, do we still wrap it inside an anonymous function? – Boyang Jul 16 '13 at 07:50
  • This is the usual idiom. But you could use Zenwolf's style if you prefer for longer functions. – Barmar Jul 16 '13 at 08:05
  • As I can see people usually write it this way. try not to do this in a big loop. I mean like for(var i = 0; i < 1000; i++). – yaoxing Jul 16 '13 at 08:23
2

You need a closure because all your event handler functions are referencing the same variable i. The for loop updates this, and when the loop is done the variable contains 2. Then when someone clicks on one of the DIVs, it accesses that variable.

To solve this, each event handler needs to be a closure with its own variable i that contains a snapshot of the value at the time the closure was created.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • You said you know the correct way to write it, you just wanted an explanation of why that's needed. I'm not sure what code I can write that will illustrate the answer -- your code does that just fine. – Barmar Jul 16 '13 at 08:03
1

I suggest that you read this article

JavaScript hoists declarations. This means that both var statements and function declarations will be moved to the top of their enclosing scope.

As @Barmar said in his answer above, the variable i is being referenced by both the event handlers.

You should avoid declaring functions inside loops. Below there is some code that does what you need.

I assume that you're using jQuery.

function MyFunc() {

    for (var i = 0; i < 2; i++) { // i= 0, 1
        var myDiv = $('<div>');

        $('body').append(myDiv);
    }
    $('div').on('click', function() {
        alert($(this).index());
    });
}

var myFunc = new MyFunc();
Dimitris Zorbas
  • 5,187
  • 3
  • 27
  • 33
0

The "alert()" call happens after the for-loop completed, which means that the value of "i" will be the last value for anything after that. In order to capture individual values of "i", you must create a closure for each value by creating a new function:

function MyFunc() {

    function alertFn(val) {
        return function () {
            alert(val);
        };
    }

    for (var i = 0; i < 2; i++) {
        var myDiv = $('<div>');
        myDiv.click(alertFn(i));
        $('body').append(myDiv);
    }
}

var myFunc = new MyFunc();

The closure captures the value of "i" at the time it was passed into the function, allowing alert() to show the value you expect.

Zenwolf
  • 56
  • 2