JavaScript closure within eventlistener

2019-09-12 11:11发布

问题:

<button>test</button>
<button>test</button>
<button>test</button>
<button>test</button>
<button>test</button>
<script>
var nodes = document.getElementsByTagName('button');
function clicked(i){
    console.log('pass');

    // Closure
    // return function(){
    //  console.log(i);
    // }

}
for (var i = 0; i < nodes.length; i++) {
   nodes[i].addEventListener('click', clicked(i));
}
</script>

I am try to have a fully understanding on js closure, the above function add even listener to the buttons. it console log 'pass' 5 times and then does nothing when button clicked. but if i uncomment out the closure bit (return), console.log will echo out the i, but not log 'pass'. I did find relevant answer but i don't get why with closure onclick doesn't log the 'pass' string when button clicked instead does log out the i.

How do JavaScript closures work?

回答1:

What you are doing here is directly calling the function clicked instead of setting it as an event handler.

i don't get why with closure onclick doesn't log the 'pass' string when button clicked instead does log out the i.

well, your closure was

return function(){
  console.log(i);
}

Why would that log anything except i ?

The function that creates this closure should log "pass" five times during page initialization (as you call five times in the loop, each time it logs a line and returns the closure).

You could use bind.

nodes[i].addEventListener('click', clicked.bind(nodes[i], i));

This will give you i as a parameter to the function, so that you can do

console.log("pass", i);


回答2:

Attempting to answer the original question as asked...

but i don't get why with closure onclick doesn't log the 'pass' string when button clicked instead does log out the i

Go back to your code without the commented out section...

<button>test</button>
<button>test</button>
<button>test</button>
<button>test</button>
<button>test</button>
<script>
    var nodes = document.getElementsByTagName('button');
    function clicked(i) {
        console.log('pass');

         //Closure
         return function(){
          output(i);
         }

    }
    function output(i) {
        alert('The value of the closure i for this button is: '+i);
    }
    for (var i = 0; i < nodes.length; i++) {
        nodes[i].addEventListener('click', clicked(i));
    }
</script>
<style>
    html {
        counter-reset: button-count -1;
    }

    button:after {
        content: ' ' counter(button-count);
        counter-increment: button-count;
    }
</style>

(I also added a little extra to help explain)

When you initialise the event listeners in the for loop, you are passing the return value of clicked(i) as the function pointer to be called when the click occurs on node[i]. In your original code, the return value is a reference to an anonymous function defined as:

function(){
  console.log(i);
 }

So this is what is executed when you click button i - not clicked. clicked is only executed during the for loop. You will see pass one time for each button in the console due to the for loop.

The lexical scope for the anonymous function includes the scope of it's containing function clicked so the parameter i is in scope for the anonymous function. Each time clicked terminates, during the for loop, a separate closure is created on the parameter i, preserving it's value for that particular call.

In the code above, I created a function called output that receives a parameter i, so this

function(){
  output(i);
}

... is called when you click button i: also having a closure on i.

I also added some CSS styling to number the buttons.