Binding listeners inside of a for loop : variable

2019-07-27 10:54发布

问题:

I've a variable scope problem and I don't understand why this occurs and how to get rid of it :

    var items = ['foo', 'bar'];
    for (var index in items) {
        var item = items[index];
        var selector = '.'+item+'-class';
        $(selector).bind('click', function() {
            console.log("class: "+$(this).attr('class'));
            console.log("selector: "+selector);
            console.log("item: "+item);
        });
    }

Considers that this code execute itself over the following HTML :

<div class="foo-class">Foo</div>
<div class="bar-class">Bar</div>

Clicking on "Foo" echoes the right class (i.e. "foo-class") in the first line but the selector and the item name following are related to bar. I think that the problem is that the second iteration of the loop reset the variables used in the first one.

I thought that the declaration inside of the loop should clearly declare their scope at this level. Am I wrong ? Why ? How can I fix it ?

I'm not seeking a workaround, I want something clean and a better comprehension of javascript variable scope mecanism.

Here the jsfiddle.

Thanks !

回答1:

Here's your fiddle example updated.

var items = ['foo', 'bar'];
for (var index in items) {
    (function() {
        var item = items[index]; 
        var selector = '.' + item + '-class';
        $(selector).bind('click', function() {
            console.log("class: " + $(this).attr('class'));
            console.log("selector: " + selector);
            console.log("item: " + item);
        });
    })();
}​

Creating an anonymous function will define a new scope for each of your defined variables

TIP: Try to create a separate function to do the bind, just to keep your code cleaner.



回答2:

It's always the same with these for-loops (google it). JavaScript does not have block scope but function scope, so when an item is clicked the one variable selector has the value it had after the last loop run (same for the variable item).

To solve the problem, you need another closure in you loop which stores the variables in its own scope. That means you need to execute a function for each loop run.



回答3:

The issue is not strictly about variable scope. The anonymous function runs at the time the click event is triggered, not when you are defining it in the loop. Consider the following which is functionally identical to your example:

var items = ['foo', 'bar'];

for (var index in items) {
    var item = items[index];
    var selector = '.'+item+'-class';
    $(selector).bind( 'click', test );
}
​
function test() {
    console.log("selector: "+selector);
}

This (hopefully) demonstrates what's happening: the global variable selector in the function is, at the time the function is being called, the same in both cases ("bar").



回答4:

var items = ['foo', 'bar'];
for (var index in items) {
    (function(i){  
    var item = items[i];
    var selector = '.'+item+'-class';
    $(selector).bind('click', function() {
            console.log("class: "+$(this).attr('class'));
            console.log("selector: "+selector);
            console.log("item: "+item);
        });
    })(index);
}

Fiddle here.



回答5:

Vars "selector" and "item" are references to a location where you store values, and the values of those two at the moment you click one of the htl elements is the one frome the last loop.