1

I do not understand the behavior below.

I am expecting the script below to do the following:

  1. Get all elements with the class 'button'
  2. For each element with button, assign an onclick event
  3. When the user clicks a button, there's an alert with the current value of i when the onclick was assigned.

So I would expect clicking "Button 1" would alert "I am button 1".

Instead all 3 buttons alert "I am button 3". It looks like the i counter's value isn't be retained.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Test</title>
    </head>
    <body>
        <p class="button">Button 0</p>
        <p class="button">Button 1</p>
        <p class="button">Button 2</p>
        <script>
            var buttons = document.getElementsByClassName('button');
            for (var i=0 ; i < buttons.length ; i++){
                buttons[i].onclick = function(){
                    alert("I am button " + i);
                };
            }

        </script>
    </body>
</html>

Why does this happen? How can I achieve the desired behavior?

Scribblemacher
  • 1,518
  • 1
  • 16
  • 31

2 Answers2

2

Try this. It is called Closures. It captures the variable i and because i is not deleted after the loop, it calls the event handlers with the current value of i. So you need a local variable, which you can get by executing function. So I create a function, pass a variable i to him, and in the function scope I have a variable, local to the function, param, which is new per iteration.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Test</title>
    </head>
    <body>
        <p class="button">Button 0</p>
        <p class="button">Button 1</p>
        <p class="button">Button 2</p>
        <script>
            var buttons = document.getElementsByClassName('button');
            for (var i=0 ; i < buttons.length ; i++){
                buttons[i].onclick = (function(param){
                    return function(){
                    alert("I am button " + param);
                      };
                })(i);
            }

        </script>
    </body>
</html>

Or you can use ES6 feature let

<!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
            <title>Test</title>
        </head>
        <body>
            <p class="button">Button 0</p>
            <p class="button">Button 1</p>
            <p class="button">Button 2</p>
            <script>
                var buttons = document.getElementsByClassName('button');
                for (let i = 0 ; i < buttons.length ; i++){
                    buttons[i].onclick = function(){
                        alert("I am button " + i);
                     }
                }

            </script>
        </body>
    </html>
Community
  • 1
  • 1
Suren Srapyan
  • 66,568
  • 14
  • 114
  • 112
  • Wow. This got me because (1) I didn't understand closures or their purpose and (2) assumed that `i` no longer existed once the for loop was concluded. – Scribblemacher Oct 19 '16 at 14:57
0

the callback button.onclick is a closure, it has in the stack the reference to the i variable that is equal to the last value from the loop

Karim
  • 8,454
  • 3
  • 25
  • 33