Understanding javascript closure with event handlers
Most of this will work the way you expect it to, except this part:
button.addEventListener("click", function (e) {
alert(i);
}, false);
One might expect that each addEventListener
gets three parameters:
"click"
- The result of the function
false
If that were the case, the five event listeners would look like this:
button.addEventListener("click", alert(0), false);
button.addEventListener("click", alert(1), false);
button.addEventListener("click", alert(2), false);
button.addEventListener("click", alert(3), false);
button.addEventListener("click", alert(4), false);
However, it isn't the result of the function that is set as the second parameter, it is the function itself. So in actuality, here are your five event listeners:
button.addEventListener("click", function(e){alert(i);}, false);
button.addEventListener("click", function(e){alert(i);}, false);
button.addEventListener("click", function(e){alert(i);}, false);
button.addEventListener("click", function(e){alert(i);}, false);
button.addEventListener("click", function(e){alert(i);}, false);
As you can see, the second parameter for all of them is the following function:
function(e){
alert(i);
}
So when you click on one of these buttons, the above function will execute. So you click on a button and the function executes, here's the operative question: what is the value of i
? The context is important here. The context is not the middle of the loop, the context is that you've clicked on the button and fired this function.
A smart guess would be that i
is undefined in this context. But there's this tricky thing that happens when a function is created inside another function: the sub-function has access to the variables in the parent function. So i
is still available! So what is i
right now? It's 5
, because we've looped it up to 5, and it remains 5.
So when you hear people talking about closures, all they really mean is "sub-function." Sub-functions are tricky because they carry the variables of their parents with them in this way.
So what if you want the buttons to alert
the number corresponding to the button clicked? You can do it if you use a function that is immediately executed, and therefore returns the result of the function, rather than the function itself. To do that, you just wrap your function expression in parens, and follow it with ();
.
var one = function(){} // one contains a function.
var two = (function(){})(); // two contains the result of a function
Let's play with this a little to see how it would affect the code.
We could make the second parameter of the event listener execute immediately like so:
button.addEventListener("click", (function (e) {
alert(i);
})(), false);
Then you would get five alerts, 0-4, immediately upon loading the page, and nothing when clicking, because the function didn't return anything.
That's not what we really want. What we actually want is to get the addEventListener
to fire from within that loop. Easy peasy--just wrap it in a function that executes immediately:
(function(i){
button.addEventListener("click", function (e) {
alert(i);
}, false);
})(i);
Note that instead of ();
we have (i);
now, because we're passing the parameter i
to it, but other than that it's all the same. Now the function is fired immediately, a.k.a. we're going to get the result of the function immediately. What is the result of this new immediately executing function? It actually changes depending on which iteration of the loop it is in. Here are the results of the function for each iteration of the loop:
button.addEventListener("click", function (e) {
alert(0);
}, false);
button.addEventListener("click", function (e) {
alert(1);
}, false);
button.addEventListener("click", function (e) {
alert(2);
}, false);
button.addEventListener("click", function (e) {
alert(3);
}, false);
button.addEventListener("click", function (e) {
alert(4);
}, false);
So you can see now that the five functions that will fire on click each have the value if i
hardcoded in there. If you get this, you get closures. Personally, I don't understand why people overcomplicate the issue when they explain it. Just remember the following:
- Sub-functions have access to their parent's variables.
- Functions that are used as expressions (e.g. assigned to a variable or something, not defined like
function newFunc(){}
) are used as a function and not as the result of the function. - If you want the result of the function, you can execute the function immediately by wrapping it in
( ... )();
Personally I find this a more intuitive way to understand closures without all the crazy terminology.
Write your code like
function initButtons() {
var body = document.body, button, i;
for (i = 0; i < 5; i++) {
(function(i) {
button = document.createElement("button");
button.innerHTML = "Button " + i;
button.addEventListener("click", function (e) {
alert(i);
}, false);
body.appendChild(button);
}(i));
}
}
initButtons();
Otherwise, all the alert will show the last result of i
, because in your code i
is in the global scope and it's getting updated every time loop happens. In this example, the wrapper/anonymous here function just separates the scope.
Demos : your code example and my code example.