Variable Access inside Functions
In JavaScript, functions are first-class citizens. You can create them
everywhere, pass them around as arguments, slice 'em, dice 'em and
smoke 'em.
Keep in mind, when a function is invoked, it has access to other
variables (its context), but access is by reference not by
value. From my perusal of Stack Overflow, this feature is huge
source of misunderstandings.
Let's suppose we have some list elements defined on a web page:
<ul id="my-list">
<li> One </li>
<li> Two </li>
<li> Three </li>
<li> Four </li>
</ul>
And we'd like to add a click
event to each one. With jQuery, this
would amount to:
var items = $("#my-list li").click( function(e) {
// …
});
Without jQuery, we just have to create a little loop. Something like:
var items = document.getElementById("my-list").getElementsByTagName("li");
for (var index = 0; index < items.length; index++) {
items[index].onclick = function(e) {
alert("You clicked on item: " + index);
}
}
Easy. Wait. Do you notice the bug? The function we created for the onclick
has access to the index
variable (as it is in the function's context), but
since this will be called later (when you click on the element), the value of
index
will be its current value, not the value it had when the function
was defined.
In other words, clicking any element will have our alert say, You clicked on item: 4
No, this isn't one of JavaScript's bad parts, but this seems to surprise
people who may expect a function definition to get a copy of the state.
Now that we understand the problem, what is the solution?
When a function is invoked, it gets a copy of the values passed as parameters,
so if we pass the index
as a parameter, our problem is solved. Ah, but the
onclick
event is doing the calling to our function, and isn't planning on passing
values that we want.
The trick is to make a new function that we gets our index
as a parameter.
function itemClicker(num) {
alert("You clicked on item " + num);
}
But since the onclick
event needs a function, we'll have our new function create
it and pass it back:
function itemClicker(num) {
return function(e) {
alert("You clicked on item " + num);
};
}
Now, the onclick
assignment looks like:
items[index].onclick = itemClicker(index);
Why sure, we could do this entire thing inline:
items[index].onclick = (function(num) {
return function(e) {
alert("You clicked on item " + num);
};
})(index);
But the inline form seems a little more difficult to grok.
Wanna see this is action?
Tell others about this article: