0

The buttons below output their numbers in the console when clicked. Fire 1 should output 1, Fire 2 should output 2, and so on.

I am using a for loop to pass 1–4 to the functions that call fire(n), but the output is never what I expect when I click any of the buttons. I tried following an answer to this question, but it is not working for me.

If I use the code I commented out instead of the for loop, everything works fine. Why will this not work if I use the for loop?

function fire(n) {
    console.log(n);
}

for (var i = 1; i <= 4; ++i) {
    $("#fire" + i).on("click", fire(i));
}

/*
$("#fire" + 1).on("click", function(){fire(1)});
$("#fire" + 2).on("click", function(){fire(2)});
$("#fire" + 3).on("click", function(){fire(3)});
$("#fire" + 4).on("click", function(){fire(4)});
*/
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="fire1">Fire 1</button>
<button id="fire2">Fire 2</button>
<button id="fire3">Fire 3</button>
<button id="fire4">Fire 4</button>
Community
  • 1
  • 1
Jon Kantner
  • 593
  • 2
  • 10
  • 29
  • 1
    `on("click", fire(i));` you are calling `fire` not setting it as a callback – Patrick Evans May 17 '16 at 17:31
  • Possible duplicate of [JavaScript closure inside loops – simple practical example](http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) – Patrick Evans May 17 '16 at 17:32
  • there is no need for using `for` in this example –  May 17 '16 at 17:47

3 Answers3

3

You need to pass a function to the click handler. You are passing undefined. This is because fire(i) runs the function, so you are setting its return value - undefined - as the handler.

You need to generate a new function for each iteration - a closure - to "capture" each i value.

function fire(n) {
    return function(){
        console.log(n);
    }
}

for (var i = 1; i <= 4; ++i) {
    $("#fire" + i).on("click", fire(i));
}

Notice how now the fire() function is returning something. It's returning a function. A different one for each click handler, one that "captures" each iteration's i value.

gen_Eric
  • 223,194
  • 41
  • 299
  • 337
3

When you are doing $("#fire" + i).on("click", fire(i)); you are actually calling the fire function. What you would like to do is to bind the parameter of the method.

One simple way to do that is to make a function that returns a new function with the correct value, which can then be added to your listener.

function fire(n) {
    return function() {
        console.log(n);
    }
}

Another way could be to use bind, which does this for you. Something like

for (var i = 1; i <= 4; ++i) {
    $("#fire" + i).on("click", fire.bind(this, i));
}

The first parameter is what to use for this when calling the function, and then the next parameters are parameters to the original function.

Matsemann
  • 21,083
  • 19
  • 56
  • 89
0

Since each button id has the information that you want to extract and use in your loop i propose the following workaround.

function fire(n) {
    console.log(n);
}

$('button[id^=fire]').on('click',function() {
fire(this.id.split("fire")[1]);
});

/*
$("#fire" + 1).on("click", function(){fire(1)});
$("#fire" + 2).on("click", function(){fire(2)});
$("#fire" + 3).on("click", function(){fire(3)});
$("#fire" + 4).on("click", function(){fire(4)});
*/
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="fire1">Fire 1</button>
<button id="fire2">Fire 2</button>
<button id="fire3">Fire 3</button>
<button id="fire4">Fire 4</button>