1

I'm building a javascript based game with a map divided in territories. For each territory, the game checks for certain conditions and if they are met, a click function is added to that territory. This results in the following code:

for (var ter = 0; ter < territoryStateInfo.length; ter++) {
    for (var adjacent = 0; adjacent < territoryStateInfo[ter].adjacentTer.length; adjacent++) {
        var tempAdjID = territoryStateInfo[ter].adjacentTer[adjacent];
        if (/*long if statement*/) {
            console.log(ter);
            $('#territoryArea' + ter).addClass('viableTarget');
            $('#territoryArea' + ter).click(function(){
                console.log(ter);
                applyNH(ter);
                for (var ter = 0; ter < territoryStateInfo.length; ter++) {
                    $('#territoryArea' + ter).removeClass('viableTarget');
                }
            });
            break;
        }
    }
}

For some reason, the first console log reports the variable 'ter' as intended. However, as soon as the exact same ter is logged by clicking a corresponding territory it returns an undefined value. My first thought was that the value of ter would be determined at the moment I clicked the territory (which is after the loop has ended and the variable no longer exists) instead of its value when the click function is created. This, however, seems to me unlikely, since I have another piece of code that works just like this, but doesn't give an undefined value. An example is below:

var viableUtilityTargets = [];
for (var ter = 0; ter < territoryStateInfo.length; ter++) {
    for (var unit = 0; unit < territoryStateInfo[ter].occupiedByUnits.length; unit++) {
        if (/*another long ramble*/) {
            if ($('#territoryArea' + ter).hasClass !== 'viableTarget') {
                $('#territoryArea' + ter).addClass('viableTarget');
                $('#territoryArea' + ter).click(function(){
                    console.log(ter);
                    applyUtility(ter, viableUtilityTargets);
                });
            }
            viableUtilityTargets.push(territoryStateInfo[ter].occupiedByUnits[unit]);
        }
    }
}

Can anybody figure out why this variable is acting the way it is and how I could solve this? Thank you in advance!

OmniShift
  • 17
  • 5

1 Answers1

0

Change this:

$('#territoryArea' + ter).click(function(){
    console.log(ter);
    applyUtility(ter, viableUtilityTargets);
});

to this:

$('#territoryArea' + ter).click(
    (function(ter){
        return function() {
            console.log(ter);
            applyUtility(ter, viableUtilityTargets);
        };
    })(ter)
);

Wrapping the code inside an IIFE (Immediately Invoked Function Expression) will create a new scope for each iteration. The outer ter is passed to the IIFE to set it internal ter which will be unique within that scope where the event listener function is defined. In your code all the event listener will have a reference to the same ter which is then incremented using the for loop untill it becomes territoryStateInfo.length, so when a click happens the ter inside the event listener will be the length no matter what button is clicked => for loop is messing up the closure because it holds the same scope through out the whole iterations, thus all function depending on that scope will have a reference to the same variables created within that scope (the scope of the function where the for loop is), and the worst thing is that those variables in the original scope keep getting changed (even after the loop).

ibrahim mahrir
  • 31,174
  • 5
  • 48
  • 73