The “Last in a Loop” Bug
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:
-
This time we're firing off a function immediately, allowing us to run some code within a closure to avoid
ichanging values on us. - We still want the
onclickto be assigned a function to fire, so we return an anonymous function just like we did before. - We
alertthe item number just like we normally would (but this time it will work). - End the returned function.
-
End the
onclickassignment. This function gets fired off immediately because of the parentheses. We passiin, 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.