As we’ve now seen, the [[Prototype]] mechanism is an internal link that exists on one object which references some other object.

This linkage is (primarily) exercised when a property/method reference is made against the first object, and no such property/method exists. In that case, the [[Prototype]] linkage tells the engine to look for the property/method on the linked-to object. In turn, if that object cannot fulfill the look-up, its [[Prototype]] is followed, and so on. This series of links between objects forms what is called the “prototype chain”.

We’ve thoroughly debunked why JavaScript’s [[Prototype]] mechanism is not like classes, and we’ve seen how it instead creates links between proper objects.

What’s the point of the [[Prototype]] mechanism? Why is it so common for JS developers to go to so much effort (emulating classes) in their code to wire up these linkages?

Remember we said much earlier in this chapter that Object.create(..) would be a hero? Now, we’re ready to see how.

  1. var foo = {
  2. something: function() {
  3. console.log( "Tell me something good..." );
  4. }
  5. };
  6. var bar = Object.create( foo );
  7. bar.something(); // Tell me something good...

Object.create(..) creates a new object (bar) linked to the object we specified (foo), which gives us all the power (delegation) of the [[Prototype]] mechanism, but without any of the unnecessary complication of new functions acting as classes and constructor calls, confusing .prototype and .constructor references, or any of that extra stuff.

Note: Object.create(null) creates an object that has an empty (aka, null) [[Prototype]] linkage, and thus the object can’t delegate anywhere. Since such an object has no prototype chain, the instanceof operator (explained earlier) has nothing to check, so it will always return false. These special empty-[[Prototype]] objects are often called “dictionaries” as they are typically used purely for storing data in properties, mostly because they have no possible surprise effects from any delegated properties/functions on the [[Prototype]] chain, and are thus purely flat data storage.

We don’t need classes to create meaningful relationships between two objects. The only thing we should really care about is objects linked together for delegation, and Object.create(..) gives us that linkage without all the class cruft.

Object.create() Polyfilled

Object.create(..) was added in ES5. You may need to support pre-ES5 environments (like older IE’s), so let’s take a look at a simple partial polyfill for Object.create(..) that gives us the capability that we need even in those older JS environments:

  1. if (!Object.create) {
  2. Object.create = function(o) {
  3. function F(){}
  4. F.prototype = o;
  5. return new F();
  6. };
  7. }

This polyfill works by using a throw-away F function and overriding its .prototype property to point to the object we want to link to. Then we use new F() construction to make a new object that will be linked as we specified.

This usage of Object.create(..) is by far the most common usage, because it’s the part that can be polyfilled. There’s an additional set of functionality that the standard ES5 built-in Object.create(..) provides, which is not polyfillable for pre-ES5. As such, this capability is far-less commonly used. For completeness sake, let’s look at that additional functionality:

  1. var anotherObject = {
  2. a: 2
  3. };
  4. var myObject = Object.create( anotherObject, {
  5. b: {
  6. enumerable: false,
  7. writable: true,
  8. configurable: false,
  9. value: 3
  10. },
  11. c: {
  12. enumerable: true,
  13. writable: false,
  14. configurable: false,
  15. value: 4
  16. }
  17. } );
  18. myObject.hasOwnProperty( "a" ); // false
  19. myObject.hasOwnProperty( "b" ); // true
  20. myObject.hasOwnProperty( "c" ); // true
  21. myObject.a; // 2
  22. myObject.b; // 3
  23. myObject.c; // 4

The second argument to Object.create(..) specifies property names to add to the newly created object, via declaring each new property’s property descriptor (see Chapter 3). Because polyfilling property descriptors into pre-ES5 is not possible, this additional functionality on Object.create(..) also cannot be polyfilled.

The vast majority of usage of Object.create(..) uses the polyfill-safe subset of functionality, so most developers are fine with using the partial polyfill in pre-ES5 environments.

Some developers take a much stricter view, which is that no function should be polyfilled unless it can be fully polyfilled. Since Object.create(..) is one of those partial-polyfill’able utilities, this narrower perspective says that if you need to use any of the functionality of Object.create(..) in a pre-ES5 environment, instead of polyfilling, you should use a custom utility, and stay away from using the name Object.create entirely. You could instead define your own utility, like:

  1. function createAndLinkObject(o) {
  2. function F(){}
  3. F.prototype = o;
  4. return new F();
  5. }
  6. var anotherObject = {
  7. a: 2
  8. };
  9. var myObject = createAndLinkObject( anotherObject );
  10. myObject.a; // 2

I do not share this strict opinion. I fully endorse the common partial-polyfill of Object.create(..) as shown above, and using it in your code even in pre-ES5. I’ll leave it to you to make your own decision.

It may be tempting to think that these links between objects primarily provide a sort of fallback for “missing” properties or methods. While that may be an observed outcome, I don’t think it represents the right way of thinking about [[Prototype]].

Consider:

  1. var anotherObject = {
  2. cool: function() {
  3. console.log( "cool!" );
  4. }
  5. };
  6. var myObject = Object.create( anotherObject );
  7. myObject.cool(); // "cool!"

That code will work by virtue of [[Prototype]], but if you wrote it that way so that anotherObject was acting as a fallback just in case myObject couldn’t handle some property/method that some developer may try to call, odds are that your software is going to be a bit more “magical” and harder to understand and maintain.

That’s not to say there aren’t cases where fallbacks are an appropriate design pattern, but it’s not very common or idiomatic in JS, so if you find yourself doing so, you might want to take a step back and reconsider if that’s really appropriate and sensible design.

Note: In ES6, an advanced functionality called Proxy is introduced which can provide something of a “method not found” type of behavior. Proxy is beyond the scope of this book, but will be covered in detail in a later book in the “You Don’t Know JS” series.

Don’t miss an important but nuanced point here.

Designing software where you intend for a developer to, for instance, call myObject.cool() and have that work even though there is no cool() method on myObject introduces some “magic” into your API design that can be surprising for future developers who maintain your software.

You can however design your API with less “magic” to it, but still take advantage of the power of [[Prototype]] linkage.

  1. var anotherObject = {
  2. cool: function() {
  3. console.log( "cool!" );
  4. }
  5. };
  6. var myObject = Object.create( anotherObject );
  7. myObject.doCool = function() {
  8. this.cool(); // internal delegation!
  9. };
  10. myObject.doCool(); // "cool!"

Here, we call myObject.doCool(), which is a method that actually exists on myObject, making our API design more explicit (less “magical”). Internally, our implementation follows the delegation design pattern (see Chapter 6), taking advantage of [[Prototype]] delegation to anotherObject.cool().

In other words, delegation will tend to be less surprising/confusing if it’s an internal implementation detail rather than plainly exposed in your API design. We will expound on delegation in great detail in the next chapter.