scriptNode

Web development with a focus on JavaScript RSS

The “Last in a Loop” Bug

Intermediate Matt Hackett Published August 28th, 2008 by Matt Hackett

The Problem

If you've had your hands in JavaScript for a while, chances are you've ran into this bug. You've got a bunch of elements you're either adding to the DOM or a bunch of listeners you're attaching to elements. Either way, you keep getting the same result: the last value in the loop. Here's an example:

Let's say you've got a list of 5 items and you want to listen to the click event for each. Many developers would immediately think of doing it this way:

You can try this example below:

  • Item #1 - click me!
  • Item #2 - click me!
  • Item #3 - click me!
  • Item #4 - click me!
  • Item #5 - click me!

No matter which element is clicked, the same result keeps appearing: You clicked element #5. This baffles many JavaScript programmers. So what's going on here?

The issue here is a misunderstanding of closure. In the above example, it's assumed that the variable i will retain its value during the loop for that particular function call. But actually what's happening is the click function is accessing the same i variable that the for loop accessed. So anytime i is checked, its value will be the same as it was when the loop finished.

A Solution

Usually what a developer wants to accomplish is a function listening to the click event that retains the value of i as it was during that iteration in the loop. Here's a way to accomplish that behavior:

This pattern can be more than a little confusing. Let's walk through it line-by-line, starting with line 5:

  1. This time we're firing off a function immediately, allowing us to run some code within a closure to avoid i changing values on us.
  2. We still want the onclick to be assigned a function to fire, so we return an anonymous function just like we did before.
  3. We alert the item number just like we normally would (but this time it will work).
  4. End the returned function.
  5. End the onclick assignment. This function gets fired off immediately because of the parentheses. We pass i in, retaining its current loop value because of the function scope we declared on line 5. This is probably the step that confuses developers the most.

Here is that example in action:

  • Item #1 - click me!
  • Item #2 - click me!
  • Item #3 - click me!
  • Item #4 - click me!
  • Item #5 - click me!

And it works the way you'd expect! What do you think? If you don't quite understand the pattern, or have another method to use to solve this problem, I'd love to hear about it in the comments.

Read other articles tagged: ,

Comments (2)

  • Unknown user
    August 28th, 2008 daaku said…

    I find the pattern kinda ugly too.. I’ve like to use Array.prototype.forEach (not available in IE, but easy to fix):

    var els = document.getElementById(’last-one-example-2′).getElementsByTagName(’LI’);

    // convert to array since above returns a NodeList
    var arr = Array.prototype.slice.call(els);

    arr.forEach(function(el, index) {
    el.onclick = function(e) {
    alert(’You clicked element #’ + index);
    }
    });

  • Unknown user

    [...] is releasing Digital Cameras with Makeup Function Saved by ShadowOfTheAssylum on Fri 07-11-2008 The “Last in a Loop” Bug Saved by lihlii on Fri 07-11-2008 Isabel De Los Rios Calorie FAQs Saved by baruchin on Fri [...]

Thoughts?

(required)

© 2008 scriptNode