-2
<button id="btn-0">Button 1!</button>
<button id="btn-1">Button 2!</button>
<button id="btn-2">Button 3!</button>



var prizes = ['A Unicorn!', 'A Hug!', 'Fresh Laundry!'];
for (var btnNum = 0; btnNum < prizes.length; btnNum++) {
  document.getElementById('btn-' + btnNum).onclick = function() {
    alert(prizes[btnNum]);
  }
}

The reason why this doesn't work is because when a function accesses a variable outside its scope, it accesses that variable, not a frozen copy, so when a user clicks a button, they will always get undefined since the for loop has already incremented btnNum to 3. prizes[3] is undefined.

My question is, does the same hold true for the document.getElementById('btn-' + btnNum)? Is the for loop attaching 3 event handlers to btn3? Or is it still attaching a click handler to each button, even though they will all alert undefined.

I'm asking a different question, not just trying to get the code to work.

WinchenzoMagnifico
  • 3,075
  • 5
  • 20
  • 23

3 Answers3

2

My question is, does the same hold true for the document.getElementById('btn-' + btnNum)?

Yes. btnNum refers to the same variable every time (though it's directly in scope, not the closure parent scope).

Is the for loop attaching 3 event handlers to btn3?

No. document.getElementById(…) and btn are evaulated during the loop, so at that time the current value of btn really is the expected one - the last assignment was the increment, which changes the value between those evaluations of the loop body.

Or is it still attaching a click handler to each button, even though they will all alert undefined.

Yes. The alerting of undefined happens because btn is then (through the click) evaluated at a time where it carries the value 3.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

In this case, Reference to btnNum is already executes and that is why you will get 'Fresh Laundry!' in all the clicks. The reason document.getElementById('btn-' + btnNum) is working the way you expect is event is binded at the very time loop is executing but callabck function will be executed later when user clicks..

var prizes = ['A Unicorn!', 'A Hug!', 'Fresh Laundry!'];
for (var btnNum = 0; btnNum < prizes.length; btnNum++) {
  document.getElementById('btn-' + btnNum).onclick = (function(btnNum) {
    return function() {
      alert(prizes[btnNum]);
    }
  })(btnNum)
}
<button id="btn-0">Button 1!</button>
<button id="btn-1">Button 2!</button>
<button id="btn-2">Button 3!</button>
Rayon
  • 36,219
  • 4
  • 49
  • 76
  • 1
    I know thats the right answer, I'm asking a different question. – WinchenzoMagnifico Dec 16 '15 at 04:22
  • no you get undefined in all the clicks, not fresh laundry-- btnNum increments all the way to 3 – WinchenzoMagnifico Dec 16 '15 at 04:29
  • 1
    but this part makes a lot of sense "event is binded at the very time loop is executing but callabck function will be executed later when user clicks.." – WinchenzoMagnifico Dec 16 '15 at 04:30
  • 2
    @WinchenzoMagnifico, Just look at this in different way. __It matters what line of code executes at what point of time__ `document.getElementById('btn-' + btnNum).onclick` executes when `for-loop` iterates hence `btnNum` will be _0,1,2_ but `callback` executes when loop is over hence value will always be last value of `btnNum` – Rayon Dec 16 '15 at 04:35
-1

Here you go. below are my answers for your queries.

Does the same hold true for the document.getElementById('btn-' + btnNum).

No.

Is the for loop attaching 3 event handlers to btn3?

During iteration, btnNum is accessible, so its add 3 event listener all 3 buttons.

Or is it still attaching a click handler to each button, even though they will all alert undefined.

Yes its attached 3 events to 3 buttons. It will alert undefined because prizes variable is not exist and its globally available in window.prizes

var prizes = ['A Unicorn!', 'A Hug!', 'Fresh Laundry!'];
for (var btnNum = 0; btnNum < prizes.length; btnNum++) {
  document.getElementById('btn-' + btnNum).onclick = function() {
    alert(this.innerHTML);
    var no = this.getAttribute('id').split('-')[1];
    console.log(no);
    alert(window.prizes[no])
  }
}
<button id="btn-0">Button 1!</button>
<button id="btn-1">Button 2!</button>
<button id="btn-2">Button 3!</button>
Venkat.R
  • 7,420
  • 5
  • 42
  • 63
  • "*It will alert undefined because prizes variable is not exist and its globally available in window.prizes*" - definitely not. I'd suggest to have a look at [Javascript infamous Loop issue?](http://stackoverflow.com/q/1451009/1048572) – Bergi Dec 16 '15 at 04:51
  • Sorry, its working perfectly as expected. look at the running snippet attached in the answer – Venkat.R Dec 16 '15 at 06:34
  • I didn't say that your snippet was not working, I said that your explanation why closures in a loop don't work is messed up – Bergi Dec 16 '15 at 06:38