2013-10-14

The dict pattern: objects without prototypes are better maps

Using objects as maps from strings to values has several pitfalls. This blog post describes a pattern that eliminates some of them.

The pitfalls

If you are (ab)using objects as maps from strings to values, you’ll encounter several pitfalls:
  1. Inherited properties prevent you from directly using the in operator for checking for a key and brackets for reading a value:
        > var empty = {};  // empty map
        > var key = 'toString';
    
        > key in empty  // should be false
        true
        > empty[key]  // should be undefined
        [Function: toString]
    
  2. Map entries override methods, meaning that you can’t directly invoke methods on an object-as-map.
  3. You need to escape the key __proto__, because it triggers special behavior in many JavaScript engines.
For details on these pitfalls, consult the blog post “The pitfalls of using objects as maps in JavaScript”.

The dict pattern

The solution is to create an object without a prototype:
    var dict = Object.create(null);
Such an object is a better map (dictionary) than a normal object, which is why this pattern is sometimes called the dict pattern (“dict” for “dictionary”). Let’s first examine normal objects and then find out why prototype-less objects are better maps.

Normal objects

Usually, each object you create in JavaScript has at least Object.prototype in its prototype chain. The prototype of Object.prototype is null, so that’s where most prototype chains end.
    > Object.getPrototypeOf({}) === Object.prototype
    true
    > Object.getPrototypeOf(Object.prototype)
    null

Prototype-less objects are better maps

Prototype-less objects have two advantages as maps:
  • Inherited properties (pitfall #1) are not an issue, any more, simply because there are none. Therefore, you can now freely use the in operator to detect whether a property exists and brackets to read properties.
  • Soon: __proto__ is disabled. In ECMAScript 6, the special property __proto__ will be disabled if Object.prototype is not in the prototype chain of an object. You can expect JavaScript engines to slowly migrate to this behavior, but it is not yet very common.
The only disadvantage is that you’ll lose the services provided by Object.prototype. For example, a dict object can’t be automatically converted to a string, any more:
    > console.log('Result: '+obj)
    TypeError: Cannot convert object to primitive value
But that is not a real disadvantage, because it isn’t safe to directly invoke methods on a dict object, anyway.

Best practice: use a library

While this pattern works well for quick hacks and as a foundation for libraries, you should normally use a library, because it is more convenient and protects you from handling the key '__proto__' incorrectly. [1] lists a few.

Reference

  1. The pitfalls of using objects as maps in JavaScript

No comments: