JavaScript is Hard Part 1: You Can't Trust Arrays
We also know there are known unknowns; that is to say we know there are some things we do not know. But there are also unknown unknowns - there are things we do not know we don’t know. – Donald Rumsfeld, February 2002
I recently had a chance to attend a workshop by Damjan Vujnovic on advanced JavaScript, where most of the attendees (including myself) were Java developers looking to dip their toes into the world of JavaScript.
Coming from a company like Caplin I had something of a headstart, because we have been JavaScript advocates for a very long time and I have in the past worked a little bit on that side of the client/server fence. So I knew in advance about some of the quirks and idiosyncracies that I’d never understood, such as the value of this, and browser differences when interacting with the DOM, and in particular the double-equals operator which appears to have been designed by a committee of psychopaths.
But what surprised me about the workshop was that there were a number of aspects of JavaScript which not only did I not know, but I also didn’t know that I didn’t know them. In some cases these were familiar language features, such as arrays, which turn out to have surprising behaviours. In other cases they were features I hadn’t heard of in the first place.
This is the first in a short series of blog posts about things I didn’t know that I didn’t know about JavaScript. Hopefully by the end you will find a thing or two that you didn’t know either. We’ll start with a few quirks about arrays.
You can’t trust the length property
JavaScript arrays don’t really have a concept of size. You can try to retrieve an element from any position in the array, and if no element exists it will return undefined. There is no such thing as an out of bounds error. So what would be the length of this array?
var myArray = [];
myArray[3] = "element";
myArray[6] = undefined;
console.log(myArray.length);
There are two elements in the array, although one of them is undefined. So is the length 2? Or could it be 1 if the undefined element doesn’t count? It’s actually 7, because the length property always returns:The index of the last element, plus one
Fair enough. Although by that rule you would expect the element at position 6 to exist and any elements further in the array not to exist. However in this case you can’t see any difference:
console.log(myArray.length); // prints
console.log(myArray[6]); // prints undefined
console.log(myArray[10]); // prints undefined
That’s because there is a difference between an index pointing to the value undefined and not having an index at all. It just isn’t particularly easy to tell the difference.
You can’t trust arrays not to behave like objects
Arrays in JavaScript are a subclass of Object, so there is nothing to stop you using strings instead of integers as keys in your array. This is because JavaScript objects are basically just maps, and maps can use strings as keys. So what would be the length of the array in this example?
var myArray = []
myArray[2] = "elementTwo";
myArray["five"] = "elementFive";
console.log(myArray.length);
The length is 3, because that is the last numerical index plus one. The element with the key “five” is ignored by the length property.
That element also won’t appear if you iterate through the array using a for loop. The only way to get that element is to know it exists and use the key to retrieve it, or iterate the array using the for-in loop.
But, as we are about to see, there is a problem with that.
You can’t trust the array iterator
If you iterate through an array with the classic for loop then the number of times it will iterate is the length of the array. If you use the for-in loop then it will only iterate through the elements that actually exist. If you have a sparse array then this might seem more efficient.
var myArray = [];
myArray[3] = "element";
myArray[6] = undefined;
var timesIterated = 0;
for(var i=0; i < myArray.length; i++) {
timesIterated++;
}
console.log(timesIterated); // prints 7
timesIterated = 0;
for(var i in myArray) {
timesIterated++;
}
console.log(timesIterated); // prints 2
Unfortunately you cannot really use the for-in loop with arrays, because oddly enough when you iterate through the array you do not get the elements in index order. Instead you seem to get them in something like insertion order.
var myArray = [];
myArray[1000] = "elementOneThousand";
myArray[0] = "elementOne";
for(var i in myArray) {
console.log("index " + i + "=" + myArray[i]);
}
// prints "index 1000=elementOneThousand"
// prints "index 0=elementOne"
Coming from a Java background this seems odd. The lesson is, don’t use the for-in loop with arrays.
You can’t trust the typeof operator
Since arrays in JavaScript are a subclass of Object, sadly the typeof operator cannot distinguish between them.
var myArray = [];
console.log(typeof myArray); // prints "object"
The general consensus is that the best way to find out if something is an array is to use the toString method.
function isArray(obj) {
return Object.prototype.toString.apply(obj)
=== "[object Array]";
}
var myArray = [];
console.log(isArray(myArray)); // prints true
Conclusion
Arrays do follow consistent ground rules, but the ground rules may differ from what you expect. This especially applies if you approach them from a Java background, where the length is the upper bound of the array and iterating an array will always give you the elements in index order.
Next time: something a little more obscure.
This is part one of a series of posts about JavaScript quirks. For part two, click here.