The JavaScript infamous loop problem

There is no block scope in JavaScript. Once you get used to this truth, the loop issue stops to be mystery, and you can say goodbye to a bunch of quirky behaviors in your code.

The for loop is a typical construct where many programmers get surprised by JavaScript. So I decided to write this post to make the issue as clear as I can. In the next post I present a few alternative solutions.

Simple loop through an array

Let's start looking at a plain for loop that won't give any trouble at all. I have an array of mixed objects, I want to call for each of them a function that does something not relevant here:
function doSomething(msg) { // 1
    console.log(msg);
}

var messages = [ "hello", 42, { pi: 3.14 } ]; // 2

for(var i = 0; i < messages.length; ++i) { // 3
    doSomething(messages[i]); // 4
}
1. I don't care much of what this function actually does, logging the parameter is more than enough.
2. This is the array I want to loop through.
3. A local variable, i, is declared and used as temporary location for the current element in the array.
4. The function is called for each element in the array.

As I said, this way to walking through an array works fine. Still, we should be aware of the fact that the supposed loop variable is not actually limited to that scope, but it is available all over the function scope in which the for loop is placed.

This doesn't work

Let's think to something a bit more complicated. Say that I don't want to call immediately the other function, I want to create an array of function, so that I can call them later, after I do some other task.

I could (wrongly) think to refactor the previous piece of code in this way:
function doSomething(msg) {
    console.log(msg);
}

var messages = [ "hello", 42, { pi: 3.14 } ];
var buffer = []; // 1

for(var i = 0; i < messages.length; ++i) {
    buffer[i] = function() { // 2
        doSomething(messages[i]);
    }
}

// ...

for(var j = 0; j < buffer.length; ++j) {
    buffer[j](); // 3
}
1. Temporary place where storing the functions to be called in a second time.
2. For each element in the message array, I create an unnamed function that calls doSomething().
3. And after a while I call all the functions stored in the buffer.

At a first sight, it looks like we should have the same result of the first example. But here we get just a desolating list of "undefined".

What happened here? The problem is that the loop variable is not actually such. So, when in (3) we call doSomething() through the buffer, the index is the current value (actually, the length of the message array) and not its value when in (2) we assigned the function to the buffer.

What we need to do is to capture the current value of the loop variable in (2) so that would be used in (3). We can achieve this result in a couple of different ways, as we'll see soon.

No comments:

Post a Comment