Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Kai Koenig
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Kai Koenig@agentK )

Creating Objects With A Null Prototype In Node.js

By Ben Nadel on

A while back, when I was reading the Principles Of Object-Oriented JavaScript by Nicholas Zakas, I came across a tip about using Object.create() to create objects with a null prototype. Such an object wouldn't inherit from Object.prototype and would, therefore, have no keys in it. Zakas suggested that this could be used to safely create a "cache" object. I really liked this idea; but, according to the MDN (Mozilla Developer Network), Object.create() wasn't supported until IE9, which made it tough(er) to use in the browser. But, in Node.js - on the server - it can be used with full support.

When you create an object in JavaScript, it automatically inherits properties from the Object.prototype. This makes it inherently dangerous to test for key-existence using JavaScript's "in" operator as it will find keys like "valueOf" and "toString" (by traveling up the prototype chain). As such, people will often use Object.prototype.hasOwnProperty() rather than the "in" operator.

But, if you create an object with a null prototype - using Object.create() - then there is no prototype chain to traverse. As such, it gets us very close to having an object that contains zero system-provided keys.

To see this in action, take a look at the following Node.js script:

  • // Create an object that has no prototype. This will prevent any default keys from
  • // existing in the object. This means that ANY and ALL keys in this object must be
  • // user-provided, which makes it very useful for a cache container.
  • var safeCache = Object.create( null );
  •  
  • // Let's check to see if any common Object keys exist in this object.
  • [ "hasOwnProperty", "toString", "valueOf", "constructor", "__proto__" ]
  • .forEach(
  • function iterator( key, index ) {
  •  
  • console.log( "[ %s ] exists: %s", key, ( key in safeCache ) );
  •  
  • }
  • )
  • ;

When we run the above Node.js code, we get the following terminal output:

[ hasOwnProperty ] exists: false
[ toString ] exists: false
[ valueOf ] exists: false
[ constructor ] exists: false
[ __proto__ ] exists: true

As you can see, all the "common" Object.prototype keys are missing. The only key that's there is the magical "__proto__" key. This gets us really close to being able to blindly add, remove, and check for the existence of keys in the given object. I guess it's up you as to whether or not this level of risk is worth the reward of even more code simplicity.

Of course, when I refer to "code simplicity", the difference isn't really that substantial. To see something a bit more concrete, I've tried to create a super simple Cache class that encapsulates the storage of key-value pairs. In the first class, the storage is implemented as a null-prototype object; in the second class, the storage is implemented as a plain JavaScript object:

  • // In this version of the cache, we're going to use an internal object with a null
  • // prototype and then assume that no user-provided cache keys will ever conflict with
  • // native keys.
  • function SafeCache() {
  •  
  • var cache = Object.create( null );
  •  
  • // Reveal the public API.
  • return({
  • get: get,
  • has: has,
  • remove: remove,
  • set: set
  • });
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • function get( key ) {
  •  
  • return( cache[ key ] );
  •  
  • }
  •  
  •  
  • function has( key ) {
  •  
  • return( key in cache );
  •  
  • }
  •  
  •  
  • function remove( key ) {
  •  
  • return( delete( cache[ key ] ), this );
  •  
  • }
  •  
  •  
  • function set( key, value ) {
  •  
  • return( cache[ key ] = value, this );
  •  
  • }
  •  
  • }
  •  
  •  
  • var safeCache = new SafeCache()
  • .set( "foo", "Bar" )
  • .set( "hello", "world" )
  • .set( "beep", "boop" )
  • ;
  •  
  • console.log( "## Safe Cache ##" );
  • console.log( safeCache.has( "foo" ) );
  • console.log( safeCache.has( "meep" ) );
  • console.log( safeCache.has( "valueOf" ) );
  • console.log( safeCache.has( "__proto__" ) );
  •  
  •  
  • // ---------------------------------------------------------- //
  • // ---------------------------------------------------------- //
  •  
  •  
  • // In this version of the cache, we're going to use a vanilla object and then take
  • // special precautions when checking for the existence of or returning a user-provided
  • // value.
  • function SaferCache() {
  •  
  • var cache = {};
  •  
  • // Reveal the public API.
  • return({
  • get: get,
  • has: has,
  • remove: remove,
  • set: set
  • });
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • function get( key ) {
  •  
  • if ( has( key ) ) {
  •  
  • return( cache[ key ] );
  •  
  • }
  •  
  • }
  •  
  •  
  • function has( key ) {
  •  
  • return( cache.hasOwnProperty( key ) );
  •  
  • }
  •  
  •  
  • function remove( key ) {
  •  
  • return( delete( cache[ key ] ), this );
  •  
  • }
  •  
  •  
  • function set( key, value ) {
  •  
  • return( cache[ key ] = value, this );
  •  
  • }
  •  
  • }
  •  
  •  
  • var saferCache = new SaferCache()
  • .set( "foo", "Bar" )
  • .set( "hello", "world" )
  • .set( "beep", "boop" )
  • ;
  •  
  • console.log( "## Safer Cache ##" );
  • console.log( saferCache.has( "foo" ) );
  • console.log( saferCache.has( "meep" ) );
  • console.log( saferCache.has( "valueOf" ) );
  • console.log( saferCache.has( "__proto__" ) );

If you only skim this code, you might not even be able to spot the difference. But, it's there in the get() and has() methods.

To be fair, if you're already going to encapsulate the cache implementation, you might as well use the safest thing possible: .hasOwnProperty() or perhaps key-salting. But, if you're not dealing with a full-fledge cache, creating a null-prototype object can be a clever way of negotiating code simplicity and risk. And, if nothing else, it's just good to know that you can create a null-prototype object in JavaScript / Node.js.




Reader Comments

I heard about Object.create() in the context of it is evil and it is going to detroy JavaScript. I wish I would have heard the rational behind such a claim. Looking at your example, it think it makes for cleaner, more beginner friendly code. New people often do not assume they have to check hasOwnProperty, I know I assumed the IN operator would just work.

Reply to this Comment

'__proto__' in Object.create(null) is only true in node.js up to version 0.10.36. If you use node 0.11.x, 0.12.0 or io.js it is false. In the browsers I tested, it is false, too.

So maybe you should update your installation of node ;-)

Reply to this Comment

@Alex,

Ah, really good to know. I just ran `node --version` locally and get back:

v0.10.33

So, it looks like I'm *just* before the switch. I'll see about upgrading. I've only really used Node.js for R&D at this point, so I'm not locked into any particular version for anything. Thanks!

Reply to this Comment

@Miguel,

Ha ha, it won't destroy JavaScript. And, in all fairness, you really only ever have to worry about key-collisions if you think someone is going to use custom keys that conflict with things like "valueOf" and "toString". The chances of that are *probably* very small. So, protecting against those edge-cases makes the code more "correct"; but, it may not have any practical value to the user.

In any case, I'm glad I could get you to think about JavaScript / Node with a new perspective :D

Reply to this Comment

If you're using io.js or Node with the `--harmony` flag, you can also just use a Map, which is iterable.

Reply to this Comment

@Ben, I was just thinking maybe the danger is the proliferation of JavaScript ignorance. I mean prototypical inheritance seems weird to people coming from Class languages like Java. People with this background may be tempted to just create naked objects because they look more familiar. Unfortunately this will also mean they will be missing out on one of JavaScript's most powerful features. I don't think that will happen, but that may have been that guy's line of thinking.

Reply to this Comment

@Ben,

It basically just says: enable some "experimental" ES6 features. I put "experimental" in quotes, because they might have been experimental when the latest version of Node was released, but might (like Maps) have been supported in V8 for a while by now -- which is why io.js was supported.

Of course, you can also add Traceur or Babel to your build pipeline and still use it :)

Reply to this Comment

Your SaferCache class is significantly less safe as implemented, as it'll crash:

new SaferCache()
.set("hasOwnProperty", "mwhaha");
.has("anything") // crash!

You need to actually use Object.prototype.hasOwnProperty.call(cache, key).

Also, where the Object.create(null) method really shines is that, since __proto__ is falsey, assuming, like in a lot of real use cases, the only values inserted are truthy (e.g. because they're objects that contain both a value from a user and some other data like a timestamp, etc), your "has" function is simply "return !!cache[key]". I've learned that if you're ever using the "in" keyword, you're probably doing something wrong.

Reply to this Comment

@jimb the edge-case you described is a valid one and should be avoided by blacklisting certain propnames in set(). However it should be noted that the scope of the article was reading props rather than writing them, with a simplified demo script.
Nice find tho :)

Reply to this Comment

@Loopmode,

Edit: forgot about the Object.prototype.hasOwnProperty suggestion. Definitely better than blacklisting :)

Reply to this Comment

@Miguel,

Prototypal inheritance is very cool but also makes some things much harder, like maintaining the proper scope (ie, "this" reference). Of course people get around this (no pun intended) by doing things like using .bind() or `var self = this` to make sure proper references can be maintained. So, the barrier to entry, for prototype-based inheritance, is likely to be higher than class-based inheritance that people are use to.

That said, I don't really fine any inheritance easy to think about :D I always end up shooting myself in the foot!

Reply to this Comment

@Jimb, @Loopmode,

Ah, excellent catch with overwriting the .hasOwnProperty() key itself! And, great suggestion with the use of the Object.prototype to safely call hasOwnProperty().

That said, I am not sure I agree with the statement:

> I've learned that if you're ever using the "in" keyword, you're probably doing something wrong.

If you use the Object.create(null), the "in" operator is actually quite safe. At least, that's the primary point of this post I was trying to make. In fact, your edge-case doesn't break the version of the cache that uses "in" instead of "hasOwnProperty."

But, again, excellent catch on the edge case.

Reply to this Comment

In theory I like the idea of Object.create(null), but then thinking about it I never really need it. If I'm not using lodash, I only ever iterate through an object's keys with

  • Object.keys(myObj).forEach(...)

with does away with the need for filtering out ownProperties and such.

But I actually logged in to comment that I find this style of coding absolutely awful:

  • return( cache[ key ] = value, this );

I think every js style guide I've seen are against the idea of using commas that way.

Reply to this Comment

@Gotofritz,

Re: coding style, yeah, you're probably right. I only used it here because there was so little code. It's not a style that I typically use. In fact, I think the only place that I actually see this used is when you look at the output of "Uglified" JavaScript, where it compresses all the code... which is probably a good sign that "normal" JavaScript shouldn't use it :D

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.