I'm reading "Javascript: The Good Parts" and am totally baffled by what's really going on here. A more detailed and/or simplified explanation would be greatly appreciated.
// BAD EXAMPLE
// Make a function that assigns event handler functions to an array of nodes the wrong way.
// When you click on a node, an alert box is supposed to display the ordinal of the node.
// But it always displays the number of nodes instead.
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (e) {
alert(i);
}
}
};
// END BAD EXAMPLE
The add_the_handlers
function was intended to give each handler a unique number (i). It fails because the handler functions are bound to the variable i
, not the value of the variable i
at the time the function was made:
// BETTER EXAMPLE
// Make a function that assigns event handler functions to an array of nodes the right way.
// When you click on a node, an alert box will display the ordinal of the node.
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (i) {
return function (e) {
alert(i);
};
}(i);
}
};
Now, instead of assigning a function to onclick, we define a function and immediately invoke it, passing in i
. That function will return an event handler function that is bound to the value of i
that was passed in, not to the i
defined in add_the_handlers
. That returned function is assigned to onclick.
I think this is a very common source of confusion for newcomers to JavaScript. First I would suggest checking out the following Mozilla Dev article for brief introduction on the topic of closures and lexical scoping:
Let's start with the bad one:
We can tackle this problem with more closures, as Crockford suggested in the "good example". A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. In JavaScript, the environment of the closure consists of any local variables that were in-scope at the time that the closure was created:
Rather than having the callbacks all sharing a single environment, the closure function creates a new environment for each one. We could also have used a function factory to create a closure, as in the following example:
It's all about closures. In the first example, "i" will be equal to "nodes.length" for every click event handler, because it uses "i" from the loop which creates the event handlers. By the time the event handler is called, the loop will have ended, so "i" will be equal to "nodes.length".
In the second example, "i" is a parameter (so a local variable). The event handlers will use the value of the local variable "i" (the parameter).
In both examples any node that's passed has an onclick event handler bound to it (just like
<img src="..." onclick="myhandler()"/>
, which is bad practice after all).The difference is that in the bad example every closure (the event handler functions, that is) is referencing the exact same
i
variable due to their common parent scope.The good example makes use of an anonymous function that gets executed right away. This anonymous function references the exact same
i
variable as in the bad example BUT since it is executed and provided withi
as its first parameter,i
's value is assigned to a local variable called ... eh? ...i
, exactely - thus overwriting the one defined in the parent's scope.Let's rewrite the good example to make it all clear:
Here we replaced
i
in the returned event handler function withnewvar
and it still works, becausenewvar
is just what you'd expect - a new variable inherited from the anonymous function's scope.Good luck figuring it out.
It has to do with closure.
When you do the thing in the bad example,
when you click each node, you will get the latest i value (i.e. you have 3 nodes, no matter what node you click you will get 2). since your alert(i) is bound to a reference of variable i and not the value of i at the moment it was bound in the event handler.
Doing it the better example way, you bound it to what i as at the moment that it was iterated on, so clicking on node 1 will give you 0, node 2 will give you 1 and node 3 will give you 2.
basically, you are evaluating what i is immediately when it is called at the line }(i) and it got passed to parameter e which now hold the value of what i is at that moment in time.
Btw... I think there is a typo there in the better example part... it should be alert(e) instead of alert(i).