JavaScript for C# Programmers: Arrays

Another episode in the series where I talk about using JavaScript, but from a C# programmer's perspective.

This episode: arrays.

Couldn't be simpler, eh? A contiguous-in-memory set of elements all of the same type, random access by integer index (starting at zero, as decreed in the mists of time), the number of elements is fixed, bounds checking on the index, and so on. Oh, sorry, that was for C#, JavaScript is different. Yes, forget all that stuff you did know about arrays. Let's learn it all afresh.

Arrays look familiar

An array in JavaScript is an object, with some special methods that provide array-like behavior. Like C#, an array is declared with square brackets, but, unlike C#, the elements in an array can be anything you like:

var myArray = [
    true,
    42,
    NaN,
    Infinity,
    "some string",
    { name: "George", age: 42 },
    ["a", "string", "array"],
    null,
    undefined
];

Using this array, we can write code to read and write elements like this.

console.log(myArray[1]);         // outputs 42
console.log(typeof myArray[5]);  // outputs "object"
myArray[6] = 3.14159;            // replace nested array with number
console.log(myArray[6]);         // outputs 3.14159

Arrays are not fixed in length

One of the more important properties of an array is length:

console.log(myArray.length);  // outputs 9

It all seems fairly reasonable. However, check this out:

myArray[1000000] = "huh?";
console.log(myArray.length);  // outputs 1000001

The length of an array in JavaScript is not the number of elements in the array like we're used to in C#, but instead the index of the last element plus 1. It is almost acting like a sparse array: if you try and read myArray[42] that hasn't been set anywhere, for example, you will get undefined. Note in particular that this behavior means no array bounds checking, so beware.

So already the array is acting differently. But there's more: length is writeable. If you set length to something greater than it was, you are not allocating any more space: the new "logical" elements are all undefined if you try and read them. If you set length to some smaller value, any "actual" elements that are in between the old and new lengths are deleted.

Using this knowledge, adding a new element to the end of the array is simple:

var pi = [3, 1, 4, 1, 5];
pi[pi.length] = 9;
console.log(pi); // outputs [3, 1, 4, 1, 5, 9]

That is, you just set the element with index equal to the length. Or you can use the push method:

pi.push(2);
console.log(pi); // outputs [3, 1, 4, 1, 5, 9, 2]

With the same result (and, yes, there is a pop, which does what you expect).

Arrays really are objects

Before you go, well, duh, they are in C# too, I mean something much deeper than that. Remember that an object in JavaScript is a hash map with string keys (property names) and values that are whatever type you like? Well, an array is exactly the same. When you read the second element of your array, myArray[1], what's happening is that JavaScript will convert the index number to a string, "1", and use that as the key/property name to find the value. That's why arrays act like sparse arrays when you add a new element whose index is much larger than the previous one: it's merely adding a new property, a new key/value pair. (Ok, the interpreter does do some extra work to set the length property, but it's certainly not allocating a whole bunch of empty elements.)

Because an array is really an object hiding behind a special name, you can use for..in to enumerate the array elements:

var element;
for (element in pi) {
    console.log(pi[element]);
}

And it will enumerate the elements of the array. However, you should not use for..in with arrays: remember that a hash map (or dictionary) is not guaranteed to store elements in any kind of obvious order. After all the values are designed to be retrieved by key, and the hash map doesn't necessarily store items in key order. This is the main reason that for..in is not the equivalent of C#'s foreach. Instead, of course, you should use the usual for loop to enumerate the values in an array:

for (var i = 0; i < pi.length; i++) {
    console.log(pi[i]);
}

(Fun algorithmic aside: running the for..in loop above produces the elements in the correct order. I'm going to guess here that for small arrays and objects, elements are stored in sequence until some point when the whole object is large enough to be converted into a normal hash table. When hash maps are small a linear search will be faster than the whole "generate a hash code, index into the hash table, and avoid collisions if need be" algorithm. At some tipping point, it'll be faster to do the more complex version and so the container is recreated. If you recall, the .NET HybridDictionary class from System.Collections.Specialized uses this technique.)

Identifying array objects

The constructor function is Array. The typeof operator, applied to an array, returns the string value "object".

Yes, we have another weirdly thought out identification scheme. One way of working out whether a variable is an array or not is this:

var isArray = function(value) {
    return (value) &&
        typeof value === "object" &&
        value.constructor === Array;
};

The first clause in that expression is essentially checking to see if value is not null or undefined. Now that we have such a function, we can easily write other functions that can act of a single value or on an array of values.

Next time we'll look at the basics of functions. After that? Well, the world is our oyster.

Album cover for Songs 1993-1998 Now playing:
Moby - Anthem
(from Songs 1993-1998)


Loading similar posts...   Loading links to posts on similar topics...

3 Responses

#1 Dew Drop - February 11, 2009 | Alvin Ashcraft's Morning Dew said...
11-Feb-09 7:49 AM

Pingback from Dew Drop - February 11, 2009 | Alvin Ashcraft's Morning Dew

 avatar
#2 Martyn said...
01-Apr-11 8:41 AM

Hi,

This is very interesting. I understand what you're saying about not using the for..in loop with arrays, but I've been playing around with them anyway to get a feel for what's going on under the covers.

I curious about why the length property of the array never gets printed when (in theory) it should when using this iteration method.

Take this following example..

var pi = [3, 1, 4, 1, 5];
pi.someProperty = "Hello";
pi.anotherProperty = "Hello2";
pi.length = 4;
var element;
for (element in pi) {
    console.log(pi[element]);
}

This outputs:

3
1
4
1
Hello
Hello2

The two properties I've added to the Array object get printed as expected, but for some reason the length property gets omitted.

Why is this? It seems that there is some special case code that alters the behaviour of the for..in loop when it is used by an Array.

Thanks,

Martyn.

julian m bucknall avatar
#3 julian m bucknall said...
01-Apr-11 1:06 PM

Martyn: (I took the opportunity to format your comment for ease of reading: I really must get round to adding markdown support for comments.)

Logically you are right: length is ostensibly a property, so it should show up in a for..in loop over an array. Trouble is arrays are special-cased all over the place, and length is one of those special cases. From the earliest days of JavaScript, if you set abc[100] on a 5-element array abc, the length would auto-magically get set to 101, and, as you provided in your example, if you set the length to some thing less than its current value the surplus elements auto-magically disappear. Until fairly recently, property getters and setters were not available, so doing this trick with a normal property was impossible. So we just have to accept that, although length "looks like" it's a property, it isn't, not in the normal JavaScript sense of the word.

(PS: another way arrays are weird: if you inherit from an array, you lose the array-ness in the descendant. I'll have to write this one up in more detail.)

Cheers, Julian

Leave a response

Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.

  •  Emphasize with italics: surround word with underscores _emphasis_
  •  Emphasize strongly: surround word with double-asterisks **strong**
  •  Link: surround text with square brackets, url with parentheses [text](url)
  •  Inline code: surround text with backticks `IEnumerable`
  •  Unordered list: start each line with an asterisk, space * an item
  •  Ordered list: start each line with a digit, period, space 1. an item
  •  Insert code block: start each line with four spaces
  •  Insert blockquote: start each line with right-angle-bracket, space > Now is the time...
Preview of response