JavaScript objects are dynamic collections mapping a string key to anything.
1 | var foo = {}; // create new object |
The {}
new object shorthand notation is equivalent to
1 | var foo = Object.create(Object.prototype); |
The new object's prototype is Object.prototype
1 | var foo = {}; |
Thus the empty object is not really empty: it has several functions available through the prototype chain
1 | var foo = {}; |
Worst of all, these properties are hidden because they are not enumerable
1 | foo.propertyIsEnumerable('toString'); // false |
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 | {}.length // SyntaxError: Unexpected token . |
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 | Object.prototype.valueOf = function() { |
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 | var foo = Object.create(null); |
Using bare objects is more intuitive because there are no unexpected type conversions or calls
1 | foo + foo // 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 | foo.bar = 'bar'; |
Since bare objects do not inherit, you can use for ... in
loops without
checking hasOwnProperty
.
1 | var foo = Object.create(null); |
One question I asked myself: are objects created using JSON.parse
bare?
They are not:
1 | var foo = JSON.parse('{}'); |
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 | {} // creates new empty 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 | // npm install bare-objects --save |
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 | var foo = Object.create(null); |