(tl;dr: Object
s are {}
and Array
s are []
; use string keys and for .. in
only with Object
s.)
Having come up against disbelievers too often now, I've decided to take matters into my own hands and definitively conclude this argument once and for all.
Arrays may not have string keys.
Take the following oft-seen pattern:
var myArray = new Array();
myArray['foo'] = 'lol';
// an array with one element?
This is wrong. Broken. Bad, wrong, invalid. Naughty. And constantly misunderstood.
Objects
The issue stems from both terminology and misleading language design. Pretty much everything in Javascript is an object, and those that are not objects — the primitive types like integers, booleans and strings — all have object-type counterparts. This is even true for functions!
At its most basic, a Javascript object is a collection of key/value pairs. We can create our own typeless object instance using {}
notation (syntax from which the JSON data format is derived, incidentally):
var myObj = {
'foo': 1,
'bar': 2
};
We could also build up the properties individually, using obj[x]
notation:
var myObj = {}; // `new Object()` would do, too
myObj['foo'] = 1;
myObj['bar'] = 2;
This notation looks like "array" access notation due to the influence of other major programming languages and, in fact, this basic key/value functionality is more generally known as a "map" or… an "associative array". This is only a general term, though, because in Javascript they are just "objects".
Why does this terminology matter? It matters because Javascript has confused matters by providing functionality for a stricter subset of the "array" concept: the numerically-indexed Array
type.
Arrays
Introduction
The Array
type is a specialisation of objects that provides an element access API (.push
and .length
) and maintains numeric, sequential keys for you:
var myArray = []; // `new Array()` would do, too
myArray.push('house');
myArray.push('bird');
Or, again, we can do this in one push:
var myArray = ['house', 'bird'];
for (var i = 0; i < myArray.length; i++) {
console.log(myArray[i]);
}
// Output: "house" "bird"
Notice how you still access the array's elements using the obj[x]
notation. That's because the Array
stores its elements as properties in itself, alongside the various other properties that it needs in order to function (like length
and push
, plus any other implementation-defined internal properties).
Looping over properties not elements
However, this is also why looping through an Array
with for .. in
syntax is wrong -- for .. in
loops through low-level object keys, not the "elements" of this higher-level "array" abstraction:
var myArray = ['house', 'bird'];
for (var key in myArray) {
console.log(myArray[key]);
}
// Possible output: "bird" "concat" "every" "filter" "forEach"
// "house" "indexOf" "join" "lastIndexOf"
// "length" "map" "pop" "push" "reduce"
// "reduceRight" "reverse" "shift" "slice"
// "some" "sort" "splice" "unshift"
In fact, recent implementations of Javascript handle this more intelligently and will still output just 'house' 'bird'
for the above loop (*), but you cannot and should not rely on this behaviour.
Array holes
Another issue is that, due to some magic, setting myArray[5]
will automatically increase length
along with it. However, this can cause "holes" to "appear" if you use object loop syntax:
var myArray = ["hello"];
arr[100] = "goodbye";
Now myArray
has a length
of 100, but we only set two user-defined properties in the underlying object representation. Using for .. in
will yield two indexes, while the for
loop will yield 101 indexes, where the 99 new numeric ones have an undefined
value. Which one did you intend?
The Misunderstanding
Unfortunately, browser vendors aren't helping to stem the spread of the myth that objects and arrays can or should be used in the same way.
In addition to the for .. in
trickery mentioned above (*), the order of enumeration even on Array
objects is the order in which the properties were created:
var array = [];
array[2] = 'c';
array[1] = 'b';
array[0] = 'a';
for (var p in array) {
// p will be 2, 1 then 0 on IE <= 8
}
This behaviour makes it quite obvious that the usage isn't quite right, but since Internet Explorer 9 the browser will enumerate the keys in ascending order, only serving to help propagate the myth that this is the right way to do things.
Non-integer keys
Another oft-seen chunk of code looks like this:
var myArray = [];
myArray["foo"] = 42;
myArray["bar"] = 84;
console.log(myArray["foo"]);
// Output: "42"
Argh! Now the programmer has created an Array
, but is using functionality of its underlying existence as an Object
instead of the proper API tools. In particular, he or she is setting object properties, bypassing the Array
functionality completely.
It appears to work, because we can still use myArray[x]
to access object properties: this hasn't changed. But we're completely bypassing the fact that this object is, more specifically, of type Array
.
Now, because the above-described "intelligent" behaviour of some browsers works by explicitly ignoring only those object properties that it knows to be properties of the Array
prototype, when we loop over object properties we still only see those that we think we set:
var myArray = [];
myArray["foo"] = 42;
myArray["bar"] = 84;
for (var key in myArray) {
console.log(myArray[key]);
}
// Output: "42" "84"
But it's still not correct, which we only actually notice when we use a proper Array
loop and notice that .length
has no idea what's going on. And how could it? It's a number.
var myArray = [];
myArray["foo"] = 42;
myArray["bar"] = 84;
for (var i = 0; i < myArray.length; i++) {
console.log(myArray[i]);
}
// Output: (nothing)
So, **we shouldn't be looping over Array
s with for .. in
.
It also doesn't help that many reference materials (example) continue to insist upon using the terms "array" and "object" interchangeably, and the difference between Array
and "associative array" (i.e. Object
) is not at all clear from the terminology.
At least the Mozilla documentation makes this clear:
An array is a JavaScript object. Note that you shouldn't use it as an associative array, use Object instead.
How to do it properly
It's tempting to rely on the above magical browser behaviours anyway. "for .. in
lets us use string keys where for i < .length
does not, so let's just use for .. in
, right?" Wrong.
We shouldn't be using string keys in the first place.
So, in conclusion:
-
The correct way to loop through an
Array
is to use the API that it provides, so that:- You are not mixing up semantics;
- You are not relying on implementation details;
- You are not prone to unexpected and surprising behaviours;
- Your code "works" properly on more browsers and in the future;
So:
var myArray = ['house', 'bird']; for (var i = 0; i < myArray.length; i++) { console.log(i + ": " + myArray[i]); } // Output: "0: house" "1: bird"
-
The correct way to use an
Array
is with numeric keys.
If you need an "array" with string keys, you may use an "associative array": a bog-standardObject
.