0

I have this click event attached to each button and when I click on each of them, it is printing the output meant for the third button. I'm not sure what is going on.

<!DOCTYPE html>
<html>
<head>
  <title>JS Bin</title>
</head>
<body>
  <button>test1</button>
  <button>test2</button>
  <button>test3</button>
</body>
<script>
   var nodes = document.getElementsByTagName('button');
   for (var i = 0; i < nodes.length; i++) {
       nodes[i].addEventListener('click', function() {
       console.log('You clicked element #' + i);
   });
   }
</script>
</html>

when I click on any of the buttons, it is printing

"You clicked element #3"
Wild Widow
  • 2,359
  • 3
  • 22
  • 34
  • When `click` fires, it shows the last value of `i`, which is 3. You need to use closure here. Here you go http://stackoverflow.com/a/8802111/3639582 – Shaunak D May 28 '16 at 05:52

4 Answers4

3

Simple solution to this:

<!DOCTYPE html>
<html>

<head>
    <title>JS Bin</title>
</head>

<body>
    <button>test1</button>
    <button>test2</button>
    <button>test3</button>
</body>
<script>
    var nodes = document.getElementsByTagName('button');
    console.log(nodes);
    for (var i = 0; i < nodes.length; i++) {
        //converted click function into an IIFE so it executes then and there only
        nodes[i].addEventListener('click', (function (j) {
            return function () {
                console.log('You clicked element #' + j);
            }

        })(i));
    }
</script>

</html>

You should go through two concepts to understand this thing

1) Closures

2) Javascript is single-threaded and synchronous. So how does it handle events?

Here is what it is happening in your code:

==> for loop gets executed synchronously as it is part of javascript engine post which javascript handles event queue which is a FIFO (first in first out)

==> When for loop finished value of i is three which remains in memory until the function inside it executes

==> Each time it takes a value 3 and prints it.

Wang Liang
  • 4,244
  • 6
  • 22
  • 45
Rahul Arora
  • 4,503
  • 1
  • 16
  • 24
1

This is the closure with function inside a loop issue.

JavaScript closure inside loops – simple practical example

Watch out for this!

Side note: questions around this issue are frequently asked in interviews to demonstrate proficiency with JS.

Community
  • 1
  • 1
pixelearth
  • 13,674
  • 10
  • 62
  • 110
1

When this button is listening to event, at that time the value of i is nodes.length -1 that is 2. Because loop has already finished it's execution and have set value of i to 2.

So it is consoling You clicked element #3.

Such issues arise because of scope & closure

Create an IIFE and pass the value of i.

Hope this snippet will be useful

  var nodes = document.getElementsByTagName('button');
   for (var i = 0; i < nodes.length; i++) {
   (function(i){
   nodes[i].addEventListener('click', function() {
       console.log('You clicked element #' + i);
   });
}(i))

   }

Check this jsfiddle

brk
  • 48,835
  • 10
  • 56
  • 78
1

This is other way using jQuery.

$("button").each(function(e) {
    $(this).data("number", e);
}).bind("click", function(e) {
    console.log("You clicked element #" + $(this).data("number"));
});

https://jsfiddle.net/ChaHyukIm/uxsqu70t/3/

ChanHyeok-Im
  • 541
  • 3
  • 11