Howardism Musings from my Awakening Dementia
My collected thoughts flamed by hubris
Home PageSend Comment

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) {
  // &hellip;
});

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:
Click here to submit this page to Stumble It