Prototype vs. Web 2.0

Michael Geary | Sat, 2006-05-20 08:21
category

In yesterday’s Ajaxian, Rob Sanheim reviews a post that explains why programmers should not use JavaScript’s Array type for associative arrays. Array objects should be used only for integer-indexed arrays. The correct type for an associative array in JavaScript is Object, not Array.

Rob is right, of course. A JavaScript Object is an associative array or hash table. An Array is also an Object (in fact, typeof [] == 'object'), but it’s really meant for arrays indexed by integers.

It’s an easy mistake to make, especially for someone who has come from a language where the associative array type is called an array. I don’t think I’ve made this particular mistake, but I have made a similar one. I used to use the for-in loop because I liked its simplicity:

var a = [ 'a', 'b', 'c' ];
for( var i in a )
   alert( a[i] )

That looks better to me than the C-style loop:

var a = [ 'a', 'b', 'c' ];
for( var i = 0;  i < a.length;  i++ )
   alert( a[i] )

The for-in loop works on an Array because an Array is an Object, but that also means it treats the Array as an Object. It enumerates the array elements because they are also properties of the underlying Object, but it doesn’t necessarily enumerate them in order by index:

var a = [];
a[1] = 'b';
a[2] = 'c';
a[0] = 'a';
for( var i in a )
   alert( a[i] )

In the browsers I tried, this code alerts the letters in the order that they were added to the array, not in index order. But there’s no guarantee about that order either, because the for-in loop doesn’t promise any particular order of enumeration.

Worse, for-in also enumerates any extra properties or methods added to the array:

var a = [ 'a', 'b', 'c' ];
a.alert = function() {
   for( var i in this )
      alert( a[i] );
}
a.alert();

This alerts the source code for the alert method as well as the three letters in the array, because they are all properties of the underlying Object.

The same thing happens if the alert method is added via Array.prototype:

var a = [ 'a', 'b', 'c' ];
Array.prototype.alert = function() {
   for( var i in this )
      alert( a[i] );
}
a.alert();

And that’s where we get into trouble with Prototype, because it adds methods to Array.prototype. This breaks any code that uses for-in on an Array. But as we just discussed, no one should do that, so it won’t be a problem in any properly-written code. Right? Case closed.

Rob puts it more strongly:

The other problem with using Arrays as associative arrays means you can no longer extend Array.prototype, which is what Prototype 1.5 does to great effect. Prototype did break Object associative arrays in 1.4 with additions to Object.prototype, something that is fixed in 1.5 after much wailing and gnashing of teeth. Some might argue extending any of the built-in objects’ prototypes is bad form, but those people are wrong.

I wouldn’t argue that it’s bad form, but extending the native object prototypes causes real pain for two groups of people:

  • People who write JavaScript widgets to be used on other websites, and

  • People who use third-party JavaScript widgets on their sites.

It’s that whole Web 2.0 mashup thing, you know?

As a widget author, if I use Prototype, then sooner or later one of my customers will want to combine my widgets with JavaScript code from somebody who does use a for-in loop on an array—“correct” or not. Or code that uses some other library that also extends Array.prototype—even a different version of Prototype that conflicts with mine.

I love the good taste of syntactic sugar as much as anyone, and if it didn’t break other code I’d be merrily adding methods to the native objects too. But I have to make my code compatible not only with the code I control, but the code I don’t. So it’s no Prototype for me.