Friday, May 19th, 2006

Javascript Associative Arrays considered harmful

Category: JavaScript, Library, Programming, Prototype, Usability

<>p>In Javascript, you can treat any object as an associative array, similiar to a Map or Hash structure in other languages. So its just a set of key/value pairs – in JS you can add any arbitrary property on the fly. The below example is using an Object, but you could use an Array or RegExp or FooType.

javascript
< view plain text >
  1. var hardcoreBands = new Object();
  2. hardcoreBands["mathyGoodnes"] = "Dillinger Escape Plan";
  3. hardcoreBands["legendary"] = "Converge";
  4. hardcoreBands["fashionistas"] = "Every Time I Die";
  5.  
  6. for(description in hardcoreBands) {  // print out the bands with descriptions
  7.   alert(hardcoreBands[description] + " == " + description);
  8. }

Andrew Dupont has an insightful post warning against the use of the javascript Array type for associated arrays, when a simple Object would be better. He says:

In JavaScript, one really ought to use Object for a set of key/value pairs. But because Array works as demonstrated above, JavaScript arrays (which are meant to be numeric) are often used to hold key/value pairs. This is bad practice. Object should be used instead.

He argues that since Arrays (the big “A” type) in JavaScript are meant to represent numerically indexed things, using them as an associative array violates the principle of least surprise. Array.length doesn’t count properties added in the above manner. so the above example would return 0. The Array constructors also don’t allow for string keys, further demonstrating that they are really meant for numeric indexes.

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.

So use Object for associative arrays, and use Array for numeric arrays. Of course, there will always be exceptions, “no best practices”, yadda yadda – but just be aware of the danger and possible confusion if you do violate this.

Related Content:

Posted by Rob Sanheim at 6:00 am
24 Comments

+++--
3.8 rating from 132 votes

24 Comments »

Comments feed TrackBack URI

Is “type” a constant or some sort of magical method? Must be in the new release of prototype. :)

Comment by Steve — May 19, 2006

You can’t really consider JavaScript objects as analogous to HashTables etc. because the keys may only be strings. If you try and use Objects as keys then all Objects will map to the same value, since when you assign a non-string key JavaScript just calls toString on it, meaning all your keys will simply be stored as “[object Object]“.

Comment by Tim Down — May 19, 2006

:)

Comment by Mario — May 19, 2006

Good observation Tim. What do you recommend? Writing a hashtable class of our own?

Comment by Marty — May 19, 2006

Steve: heh, thanks – fixed.

Tim: true, its like a limited version of a HashTable I suppose.

Comment by Rob Sanheim — May 19, 2006

[...] In talking about Javascript Associative Arrays considered harmful, Ajaxian mentions that the Prototype library no longer extends Object.prototype as of 1.5. Welcome to the world of compatible JavaScript! (They do extend Array.prototype, but that cause far fewer headaches, if any.) [...]

Pingback by Ajaxian » Javascript Associative Arrays considered harmful at Blue Sky On Mars — May 19, 2006

Some might argue extending any of the built-in objects’ prototypes is bad form, but those people are wrong.

No one can be wrond when it comes to opinions, especially concerning Javascript. Let’s keep the unprovable blanket statements to a minimum. Everyone is entitled to their opinion, and the fact that its so easy for people who don’t know what they’re doing (and even people that do) to break functions that other people rely on is a legitimate concern.
Just because you decide to override the Object class to output your Mom’s name when toString() is called, doen’t mean everyone likes your Mom.

Comment by Dan — May 19, 2006

Stupid fingers … I meant wrong, no one can be wrong

Comment by Dan — May 19, 2006

Also, I don’t think arrays should ever store the number 2.7. I demand that everyone modify their code so that the value 2.7 is never stored in an array! Obey! Obey! Obey! Obey!

Comment by J Perry — May 19, 2006

if you add a decent toString() to your objects you could use an object as a hash key I guess.

why make such a big issue out of this… arrays are objects as well… they deserve to be abused just like regular objects… whether or not it’s smart to do so… no, but I guess people are doing far more stupid things than this.

Comment by Lon — May 19, 2006

Hmm, interesting standpoint but as far as I know, these are all internally equivalent:

var a = {'a':0, 'b':1, 'c':2};

var b = new Array();
b['a'] = 0;
b['b'] = 1;
b['c'] = 2;

var c = new Object();
c.a = 0;
c.b = 1;
c.c = 2;

So how can one be better than the other?

Comment by Menno — May 19, 2006

@Menno: Actually, in your example, a.length is undefined and b.length=0 so they aren’t internally equivalent.

Comment by sylvinus — May 19, 2006

No one can be wrond when it comes to opinions, especially concerning Javascript. Let’s keep the unprovable blanket statements to a minimum.

My blanket statement was meant to be in jest, I realize there are contexts where changing the global objects is dangerous. In general, on small, experienced teams, extending builtins can be a very good thing – if you are on a corporate team who is just learning javascript, then I think it might not be such a good idea.

Comment by Rob Sanheim — May 19, 2006

I agree that there are certain cases where it is time-saving to modify built-in type behaviours, and there are even cases when it makes sense. These are generally in tight-knit, closed environments when you have people that know what is happening. these are also cases where you meddling does not affect any other projects. I believe that all the frameworks out there that should not be screwing with objects as much as they do. I really don’t think my Array object needs 30 new methods so that I can fade a DIV.

Comment by Dan — May 19, 2006

sounds more like a problem with array.length rather than a bad practice. associative arrays are common for most any scripting language, since when did it become a bad practice?

I think using an object is a worse approach because it consumes more memory on the client machine.

Comment by A ray — May 21, 2006

A JavaScript Array is an Object. You wouldn’t save any memory by using an Array instead of an Object.

See my article linked above for more details.

Comment by Michael Geary — May 21, 2006

[...] Ajaxian » Javascript Associative Arrays considered harmful (tags: Javascript programming) [...]

Pingback by links for 2006-05-23 at /dev/caffeine — May 22, 2006

Oh no, here we go again. Are we talking here about a general language issue or implementations in some arbitrary case. The more you restrain possible options in a programming language, the less usable it becomes in different situations. When you are leading a programmers team in a software project, then you should say what is allowed and what is not (ie. “bad practice”). When you are designing a general use programming language then you should allow as wide range of functionality to a programmer as possible (without compromising simplicity ofcourse).

Comment by vulcan — May 28, 2006

There are a number of significant disadvantages of using Arrays as hashtables instead of Objects. The biggest one is that any extended prototype functionality added to the Array object will break any for…in loop unless you test for enumerability. The length() problem can be gotten around in the same manner by adding a size() method to the Array object that does the same test:


Array.prototype.size = function() {
  var i = 0;
  for (var j in this) {
    if (this.propertyIsEnumerable(j)) {
      i++;
    }
  }
  return i;
}

That’s fine for getting the number of elements, but you’re still buggered on the loops. Each and every for…in loop has to have the same this.propertyIsEnumerable(var) test unless you want breakage. Being the lazy person that I am, I’d rather save myself the trouble of writing all those extra, needless lines of code. But that’s just me.

Comment by Matt Brock — May 29, 2006

So how do you iterate over the elements of this superior hash map called an Object?

Comment by Russ — June 19, 2006

Nearly a year later, the answer is: ‘for/in’. (Also, this answer was right above the question for weeks.) The question you really want to ask is how to get the count of enumerable properties in an object without looping over all of them, which I can only assume is hideously slow.

Comment by Pete — May 19, 2007

Here it is (for what is worth after such a time.length …) a little script to create a working associative array:

String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ”); }
String.prototype.startsWith = function(s) { return this.substring(0,s.length – 1) == s; }
String.prototype.endsWith = function(s) { return this.length >= s.length && this.substring(this.length – s.length) == s; }
function AssocArray(){
}
AssocArray.prototype.size = function() {
var result = 0;
for(i in this){
if(this[i].startsWith)
result++;
}
return result;
}
AssocArray.prototype.keys = function(){
var result = new Array();
for(i in this){
if(this[i].startsWith)
result[result.length] = i;
}
return result;
}
AssocArray.prototype.values = function(){
var result = new Array();
for(i in this){
if(this[i].startsWith)
result[result.length] = this[i];
}
return result;
}

And some test to the above:

var taa = new AssocArray();
taa.testa = ‘value1a’; // or taa['testa'] = ‘value1a’;
taa.testb = ‘value2a’; // …
alert(‘taa.size = ‘ + taa.size());
var s = ”;
var k = taa.keys();
for(i in k){
s += ‘;’ + k[i];
}
alert(‘Keys are ‘ + s);
s = ”;
var v = taa.values();
for(i in taa.values()){
s += ‘;’ + v[i];
}
alert(‘Values are ‘ + s);
for(i in taa){
if(taa[i].startsWith)
alert(i + ‘=’ + taa[i]);
}

Note the test for startsWith function in the for..in loops (otherwise the functions themselves are also counted, so I test to the current value to be a String, so it has my added startsWith function)
The trim() and endsWith() functions are not used here, but might also be useful.

Hope this can be useful :)

Comment by vestrinang — July 25, 2008

Wow, nice choice of bands, and great javascript tips too!

Comment by jbaker — February 7, 2009

I created a little library to manage key value pairs.

https://github.com/scaraveos/keyval.js#readme

It uses

- an object to store the keys, which allows for fast delete and value retrieval operations and
- a linked list to allow for really fast value iteration

Hope it helps :)

Comment by scaraveos — October 3, 2011

Leave a comment

You must be logged in to post a comment.