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.
If you are (ab)using objects as maps from strings to values, you’ll encounter several pitfalls:
For details on these pitfalls, consult the blog post “The pitfalls of using objects as maps in JavaScript”.
The solution is to create an object without a prototype:
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.
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.
Prototype-less objects have two advantages as maps:
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:
But that is not a real disadvantage, because it isn’t safe to directly invoke methods on a dict object, anyway.
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.
The pitfalls
If you are (ab)using objects as maps from strings to values, you’ll encounter several pitfalls:
- 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]
- Map entries override methods, meaning that you can’t directly invoke methods on an object-as-map.
- 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.
Comments
Post a Comment