JavaScript is Spartan when it comes to built-in data structures. One commonly uses objects as maps from strings to values. This post points out three pitfalls when doing so.

Pitfall 1: inheritance and reading properties

obj

var objProto = { superProp: "abc" }; var obj = Object.create(objProto);

obj

objProto

superProp

obj

When it comes to reading properties, there are two kinds of methods in JavaScript. On one hand, methods that access the whole prototype chain of an object and thus “see” inherited properties. On the other hand, methods that access only the(direct) properties of an object and thus ignore inherited properties. As a running example, consider the object stored in the variableis an object with no own properties whose prototype is, an object with the propertyshould be interpreted as an empty map. Let’s see what operations fail to do so.

Checking whether a property exists. The in operator checks whether an object has a property with a given name, but it considers inherited properties:

> "foo" in obj false // ok > "toString" in obj true // not ok, inherited from Object.prototype > "superProp" in obj true // not ok, inherited from objProto

obj

hasOwnProperty()

> obj.hasOwnProperty("toString") false

obj

for-in

> for (propName in obj) console.log(propName) superProp

Object.prototype

> Object.prototype.propertyIsEnumerable("toString") false

Object.keys()

> Object.keys(obj) []

Object.getOwnPropertyNames()

Object.prototype

> Object.keys(Object.prototype) [] > Object.getOwnPropertyNames(Object.prototype) [ 'toString', 'hasOwnProperty', 'valueOf', ... ]

foo

obj["foo"]

obj.foo

Given thatshould be considered empty, we need the check to ignore inherited properties.provides the necessary services:How do we find out all of the keys ininterpreted as a map?looks promising:Alas, it considers inherited enumerable properties. The reason that no properties ofshow up here is that all of them are non-enumerable:lists only own properties.This method only returns enumerable properties. If you want to list all properties, you need to use. You can observe the difference by applying the methods toPropertiesadded by assigning toorare enumerable by default.

Getting a property value. The normal way of getting properties accesses all properties:

> obj["toString"] [Function: toString]

function getOwnProperty(obj, propName) { if (obj.hasOwnProperty(propName)) { return obj[propName]; } else { return undefined; } }

toString

> getOwnProperty(obj, "toString") undefined

Pitfall 2: overriding and invoking methods

getOwnProperty()

hasOwnProperty()

obj

> getOwnProperty({ foo: 123 }, "foo") 123

obj

"hasOwnProperty"

Object.prototype.hasOwnProperty()

getOwnProperty()

> getOwnProperty({ hasOwnProperty: 123 }, "foo") TypeError: Property 'hasOwnProperty' of object #<Object> is not a function

hasOwnProperty()

obj

function getOwnProperty(obj, propName) { if (Object.prototype.hasOwnProperty.call(obj, propName)) { return obj[propName]; } else { return undefined; } }

Pitfall 3: the special property __proto__

__proto__

"__proto__"

"__proto__"

"__proto__"

"%__proto__"

"%__proto__"

"__proto__"

There is no built-in way in JavaScript to only read own properties, but you can easily implement one yourself:With that function, the inherited propertyis ignored:The functioninvoked the methodon. Normally, that is fine:However, if one adds a property towhose name isthen that property overrides the methodandceases to work:This problem can be fixed, by directly referring to. This avoids going throughto find it:On many JavaScript engines, the propertyhas special meaning: Setting it sets an object’s prototype. As we avoid inheritance, having the wrong prototype is less of an issue, but setting it is still a performance issue and will throw an error for non-object values. Hence, whenever arbitrary keys are involved, you need to escape the key. Note that you also need to escape the escaped version of(etc.) to avoid clashes. That is, if you escapeasthen you also need to escapeso that it doesn’t override aentry when it is used.

Mark S. Miller mentions real-world implications of this pitfall, in the email “Why we need to clean up __proto__” (which inspired this post):

Think this exercise is academic and doesn't arise in real systems? As observed at a support thread, until recently, on all non-IE browsers, if you typed "__proto__" at the beginning of a new Google Doc, your Google Doc would hang. This was tracked down to such a buggy use of an object as a string map. (To avoid such problems, Caja is shifting to using StringMap.js, which does seem safe on all platforms.)

Best practices

StringMap.js by Google’s es-lab

stringmap.js by Olov Lassus

The author’s strmap project

Related reading

There are many applications for using objects as maps. If all property names are known statically (at development time) then you only need to make sure that you ignore inheritance and only look at own properties [1]. If arbitrary names can be used, you should turn to a library to avoid the pitfalls mentioned in this post. Two examples: