The Perils of for..in

On one web page that was comparing long versions of JavaScript code with shorter equivalents, one particular supposed shortcut stood out. It stood out because the two pieces of code mentioned do completely different things so that the one isn't a shortcut for the other.

The particular "shortcut" was labelled JavaScript foreach Loop Shorthand and suggested this shortcut is appropriate when you can't use Array.forEach().

The "long" version was shown as:

for (var i = 0; i < allImgs.length; i++)

and the "short" version was shown as:

for(var i in allImgs)

Now to start with, neither of these is equivalent to Array.forEach() - all three do different things and so none of them can be considered a shortcut for any of the others. Let's look at some example code that illustrates the difference between the three. We'll start by creating an array and then see how the several different loops will process it.

var myArray = ['a',,,'d',,'f'];
Array.prototype.hasIndex = function(prop){return this.hasOwnProperty(prop) && /^0$|^[1-9]\d*$/.test(prop) && prop <= 4294967294;}
 
console.log('for loop');
for (var i = 0; i < myArray.length; i++) console.log(myArray[i]);
 
console.log('for..in');
for (var i in myArray) console.log(myArray[i]);
 
console.log('forEach');
myArray.forEach(function(a) {console.log(a);});

With the for loop we get the following: "a", undefined, undefined, "d", undefined, "f"

With the for..in loop we get: "a", "d", "f", function (prop){return this.hasOwnProperty(prop) && /^0$|^[1-9]\d*$/.test(prop) && prop <= 4294967294;}

With the forEach we get: "a", "d", "f"

As you can see we get different results for each of these and so none of them can be considered a shortcut for any of the others.

Now the particular array that I chose to demonstrate this is what is known as a sparse array. A sparse array has gaps in the array where some of the entries in the array not only don't have values but they don't exist at all. The forEach loop is specifically set up to process sparse arrays and so only the three entries out of the six that are in the array that actually exist get output. The for loop on the other hand always processes arrays as if they were dense arrays and so it returns undefined for every entry in the array that doesn't exist. The for..in loop works for iterating over any type of object and is not restricted to arrays - it returns all of the properties and methods belonging to the object and not just the numbered entries of the array and so it processes not only the numbered array entries of the array that actually exist (as forEach does) but also any other properties and methods attached to the object - and so returns the hasIndex method as well.

If we look at an object representation of the array the processing of the for..in and forEach will be easier to understand. Effectively we could replace the first two lines of the above code with the following object definition:

var myArray = {0:"a", 3: "d", 5:"f", hasIndex:function(prop){return this.hasOwnProperty(prop) && /^0$|^[1-9]\d*$/.test(prop) && prop <= 4294967294;});

Now if we had what is known as a dense array where all of the entries have values then the for loop and the forEach loop would both return the same result but with the forEach being the shorter of the two. So unless you specifically need to ensure that missing array entries get processed the forEach is the shorter way of processing arrays.

The for..in loop isn't completely useless for processing arrays though, we just need to add an extra piece of code to our loop to get it to skip over the properties and methods that are not a part of the array content. The simplest way to do this is:

console.log('modified for..in');
for (var i in myArray) if (myArray.hasIndex(i)) console.log(myArray[i]);

This code now returns identical results to the much shorter forEach version (and for dense arrays the same result as the for loop). So now you know why I included that particular method in the code - it is a way of using a for..in loop to process sparse arrays as a longer alternative to using forEach.

So in conclusion, for..in is not a shortcut for processing arrays at all - it is the longest of the three variants. You might consider that to be the long version of code for processing sparse arrays with forEach being the shortcut. You can also consider the for loop to be the long version for processing dense arrays with foreach also being the shortcut. The exception case is where you need to ensure that the array is processed as if it were a dense array even if it is a sparse array in which case the for loop version is the only one that will process the missing entries in the array.

 

This article written by Stephen Chapman, Felgall Pty Ltd.

go to top

FaceBook Follow
Twitter Follow
Donate