Using objects as hashtables in Javascript

Welcome to my first blog post ever. Inspired by discussions with some of my co-workers last week about using plain old Javascript objects as arbitrary key/value stores, I wanted to share my observations on this subject.

Everybody has done it, me too. And everytime I did it, I got the feeling that it’s not quite right. The fact that every object in Javascript is just a property bag can easily seduce developers to use simple objects in order to emulate hashtable functionality without too much consideration:

1
2
3
4
var table = {};
table["myKey"] = 3.3;
table["someOtherKey"] = new Date();
console.log(table["myKey"]); // prints "3.3"

This looks like a flawless method to store key/value pairs, right? It is a method to store key/value pairs, but it’s far from flawless.

Flaw #1: An empty object has members by “inheritance”

A Javascript object created by an object literal is empty, but it sports a prototype chain. In fact, the table object from the previous code snippet looks like this in reality:

Prototype chain of a plain object

Depending on the host environment there might be even more properties defined on the prototype object (like __proto__ in many browsers). Indeed, these “inherited” properties are non-enumerable, so they won’t show up in a for-in loop, but there are frameworks that think it would be a good idea to extend Object.prototype with custom properties.

To get around these obstacles we have to ensure to only enumerate an object’s very own properties, either the good old ugly way …

1
2
3
4
5
for (var prop in table) {
if (Object.prototype.hasOwnProperty.call(table, prop) {
// do stuff with prop
}
}

… or the much nicer way using the Object.keys function coming with ECMAScript 5 which returns an array of the given object’s own enumerable properties:

1
2
3
Object.keys(table).forEach(function (prop) {
// do stuff with prop
});

Another way to avoid “inherited” properties is to make sure that an object has no prototype using Object.create and providing null as the first parameter (which will become the prototype for the new object):

1
var objectWithoutPrototype = Object.create(null);

This way, you get a really empty object:

Prototype chain of a plain prototype-less object

Flaw #2: Property access coerces all keys to strings

Unexperienced Javascript programmers might not be aware of the fact that keys in objects always are strings. If you provide a number or boolean value it will get coerced to its string representation:

1
2
3
4
var table = {};
table["1"] = "firstValue";
table[1] = "secondValue"; // converts 1 to "1"
console.log(table["1"]); // prints "secondValue"

Flaw #3: Old Internet Explorers

This flaw is not caused by Javascript’s nature but rather by an implementation of Javascript called “JScript”: Any Internet Explorer lower than version 9 exhibits an annoying behavior when determining the “enumerability” of an object’s property: it will not enumerate a property if it finds a non-enumerable property of the same name somewhere down the object’s prototype chain. For example: A property called toString will not get enumerated even if it is redefined on an object because it shadows the Object.prototype.toString property which is marked as non-enumerable:

1
2
3
4
5
6
var table = {};
table["fromString"] = "foo";
table["toString"] = "bar";
for (var prop in table) {
console.log(prop); // Prints "foo" and "bar" in Chrome, but only "foo" in IE8
}

This phenomenon can get even experienced developers to scratch their heads, it’s called the DontEnum Bug.

So, what to do now?

  • First of all, convince your manager to drop support for Internet Explorer lower than version 9 (or even better: 10)
  • Use a real hashtable implementation such as stringmap.js
  • If you can’t use third party libraries, use Object.create and Object.keys
  • If you have to support non-ECMA5 browsers, create your own hashtable implementation
  • For the curious ones: use ECMAScript 6 Harmony’s Map in environments that already support it, or otherwise consider using a shim like es6-shim or es6-collections

Please feel free to comment …