Javascript: use the forEach Method and Do not Bother with Scopes

I recently started to re-learn Javascript because I would like to build better web applications, and in order to that, I need a solid understanding of the language. The last time I was learning about Javascript, I was taught that it was a younger brother of Java, so if I had a good understanding of Java, I would be fine with Javascript. I didn’t really question the words of my teachers, and because I was able to write working Javascript code, I kept using the Java approach.

After reading about the prototype nature of Javascript, the different kind of scope rules, the closures and the helper methods such as Array.prototye.forEach(), I can say that the statement about Javascript being the younger brother of Java is completely invalid. Javascript has it own flavour, and in order to write clean code, I must understand this flavour and forget about other languages like Java for a moment.

Let’s say that I would like to do something asynchronous with the elements of an array.

// The array:
var elements = ["element1", "element2", "element3"];

This is how it is supposed to be implemented:

elements.forEach(function(element) {
  // The asynchronous action simulator
  setTimeout(function() {
    console.log(element);
  }, 100);
});

It is clean, fast and easy to understand.

Without knowing about forEach() I would start with a classic for(;;) loop:

for (var i = 0; i < elements.length; i++) {
  setTimeout(function() {
     console.log(elements[i]);
  }, 100);
}

This doesn’t work as expected, because instead of element1, element2, and element3 on the console, I’m getting undefined three times. It seems that something is wrong with the i variable, because the function works fine with elements[0]. Actually, there is not nothing wrong with i. The thing is that its value is 3 when the console.log() is called and since elements[3] does not exist, it prints out undefined.

What happens here is not obvious at first, because the scope rules in Javascript are very different from the rules of other languages like Java.

First, there is no block scope in Javascript. Defining any variable with the var keyword means that the variable is accessible from everywhere inside the function where it was defined. This is called function scope. If it is created outside of a function it’ll become a global variable. In our case, after the for (var i = 0;...) loop has finished, the i is still available, and its value is 3! (When the i < elements.length condition stops the loop, the value of i is actually 3.)

Second, Javascript always passes the arguments of a function as references. When the console.log(elements[i]) is called, it uses the referenced value of i, which still exists within the scope, because a function created in a function can access the references of the parent function, but not vice-versa.

Finally, the for(;;) loop finishes before the asynchronous methods start, and the value of i is 3 at that time, so when console.log() is finally executed, it prints the value of elements[3] - undefined - three times.

No worries, there is a way to bind the actual value of i by putting it into a different scope:

for (var i = 0; i < elements.length; i++) {
  (function(index) {
    setTimeout(function() {
       console.log(elements[index]);
    }, 100)})(i);
}

Now it works as expected. With the (function(index) {...})(i) approach, a new function is created - along with a new scope - for each iteration and the new function will hold the proper value of i in the function parameter index. (Note: I used index in order to make the example easy to understand, but using i instead of index is completely valid, and you may find similar solutions in real code.)

Here is the forEach() version again, so that you can easily compare it to the version above:

elements.forEach(function(element) {
  setTimeout(function() {
    console.log(element);
  }, 100);
});

Besides readability, the forEach() version uses less memory, because the very same function is used in each iteration, whilst the for(;;) version creates a new function every time, and these stay alive until their scope ends. Additionally, using the forEach() method takes your focus away from the mechanics of the iteration - how to iterate through an array, how to handle scopes - and lets you focus on the important part: the body of the iteration.


comments powered by Disqus