Sweet naked objects

Create and use objects without any prototype.

JavaScript objects are dynamic collections mapping a string key to anything.

1
2
3
var foo = {};             // create new object
foo.bar = 'bar'; // string -> string
foo.baz = function () {}; // string -> function

The {} new object shorthand notation is equivalent to

1
var foo = Object.create(Object.prototype);

The new object's prototype is Object.prototype

1
2
3
4
var foo = {};
Object.prototype.isPrototypeOf(foo); // true
foo instanceof Object // true
foo.constructor === Object // true

Thus the empty object is not really empty: it has several functions available through the prototype chain

1
2
3
var foo = {};
foo.toString(); // "[object Object]"
foo.valueOf(); // Object {}

Worst of all, these properties are hidden because they are not enumerable

1
2
3
4
5
foo.propertyIsEnumerable('toString');  // false
for (var property in foo) {
console.log(property);
}
// nothing is printed

Even the query propertyIsEnumerable is not enumerable itself

foo.propertyIsEnumerable('propertyIsEnumerable');  // false

Of course these design decisions in EcmaScript 5 are not accidental. Prototypical JavaScript inheritance relies on having a prototype for every object. Most object design patterns rely on being able to distinguish own property vs inherited, etc. But it also leads to weird things like these (giving JavaScript a bad name):

1
2
3
4
5
{}.length         // SyntaxError: Unexpected token .
({}).length // undefined
{} + 1 // 1
{} + {} // NaN
({} + 1).length // 16

The really surprising 16 in the last example comes from JavaScript picking to use valueOf() when an object is used in an arithmetic expression. {}.valueOf returns the object itself via Object.prototype.valueOf. So it is back to square one: adding object to number 1. So VM tries second approach: using toString. {}.toString() is a string "[object Object]" returned by Object.prototype.toString. Then 1 is concatenated to the result because when adding to a string, anything else is typecast as a string too. The final step is "[object Object]1".length which returns 16 characters.

These steps could be traced. For example I could do this (highly not recommended in actual programming)

1
2
3
4
5
6
7
8
9
10
Object.prototype.valueOf = function() {
console.log('valueOf called'); return this;
};
Object.prototype.toString = function() {
console.log('toString called'); return '';
};
({} + 1).length
valueOf called
toString called
1

So the JavaScript VM first tries to use valueOf, then tries toString.

Naked (bare) objects

There is a way to construct purely data storage key/value pairs by specifically setting new object's prototype to be undefined

1
2
3
4
var foo = Object.create(null);
foo instanceof Object // false
foo.constructor // undefined
foo.toString(); // TypeError: Object [object Object] has no method 'toString'

Using bare objects is more intuitive because there are no unexpected type conversions or calls

1
2
foo + foo         // TypeError: Cannot convert object to primitive value
(foo + 1).length // TypeError: Cannot convert object to primitive value

A bare object is simply not meant to be used for nothing but storing and retrieving values via string keys. It still can be used with dot / array notation, used to access scope via this and sealed / frozen just like normal object

1
2
3
4
5
6
7
8
foo.bar = 'bar';
foo['bar'] // returns 'bar'
Object.freeze(foo);
foo.baz = 'baz'; // trying to add new property
// either does nothing silently
// or throws TypeError in strict mode
foo.getBar = function() { return this.bar; }
foo.getBar(); // returns value 'bar'

Since bare objects do not inherit, you can use for ... in loops without checking hasOwnProperty.

1
2
3
4
var foo = Object.create(null);
foo.bar = 'baz';
for(var property in foo) { console.log(property); }
// prints bar

One question I asked myself: are objects created using JSON.parse bare? They are not:

1
2
var foo = JSON.parse('{}');
foo instanceof Object // true

What I call naked or bare object in this case is what Dr. Alex calls "dictionaries" in his book "Speaking JavaScript" in chapter 17.

Shorthand notation

I would like to use naked object for data, but typing Object.create(null) is way too long compared to {}. I need a shorthand notation that could be expanded automatically. I picked angle brackets:

1
2
3
{} // creates new empty object
[] // creates new empty Array
<> // creates new naked object

The two angle brackets are not used by the existing JavaScript language, and could be detected using simple regular expression. Since I run nodejs most of the time, I could use simple regular expression or even string substitution to replace <> with Object.create(null) via node module load hook, like I did for dotdot notation. So I wrote and published the bare-objects module. You can use it from your Node programs:

1
2
3
4
5
6
// npm install bare-objects --save
require('bare-objects'); // somewhere in the beginning
var foo = <>;
Object.isPrototypeOf(foo); // false
foo.bar = 'bar';
foo + foo // TypeError: Cannot convert object to primitive value

Sweet notation

Using regular expressions via a module load hook is nice, and works, but I love what sweetjs is doing: extremely powerful source code transformations using abstract syntax trees via simple macro rules and JavaScript preprocessor. The same transform to replace every instance of <> could be expressed as a tweet

macro <> { rule {} => { Object.create(null) } }

Then one could run sweetjs transpiler to generate valid JavaScript:

var foo = <>; // var foo = Object.create(null);

You can see this in action using sweetjs online editor. Just copy/paste the above macro expression and then var foo = <>; to see the equivalent expression on the right. I added sweet-naked-objects.js to the bare-objects repo. Unfortunately, the current transpiler does not understand the exported macro with name <>, so you cannot use this from your code yet, unless you copy paste the macro into each file that uses <> notation :(

Performance

I compared read/write access speeds between an object created using {} vs Object.create(null) notation. They were essentially the same, having a prototype pointer has no impact on accessing other properties.

An interesting and important performance difference was found when I compared purely the object creation step. Turns out, Chrome browser really optimizes creating regular objects using {} notation - they were created on average 20 times faster than objects created using Object.create(null). So if you create lots of objects, keep this in mind. I suspect that default empty objects are really a just memory alloc and memcopy the prefilled object meta information, while Object.create(null) actually executes custom function to create and fill the object.

I also compared JSON.stringify speed speculating that serializing objects without walking up the prototype chain should be slightly faster. In fact it is faster by a negligible amount: about 3%.

Conclusion

Creating naked JavaScript objects is simple and can safeguard against weird WTF errors. There are even ways to extend the JavaScript itself by adding new syntax (I would prefer to use <>), via modifying the source code either as a preprocess or on the fly. At the same time we have to keep in mind that creating such objects incurs a performance penalty in at least one widely used modern browser.

Update

I found an interesting quirk in V8 JavaScript implementation. If you need to set the object's prototype, as described in making objects smarter you cannot use a naked object! The prototype is shown, but does not work! For example:

1
2
3
4
5
6
var foo = Object.create(null);
foo.__proto__ = {
bar: 'bar'
};
foo; // { [__proto__]: { bar: 'bar' } }
foo.bar; // undefined