The JavaScript prototype chain

So far we’ve learned what the relationship is between an object and its constructor’s prototype and what happens when we change properties set on the prototype. In particular, we learned that if you try to access a property of on object, the JavaScript engine will first look in the object itself for the property, and it if doesn’t find it there it looks in its __proto__ property, which is also the constructor’s prototype.

This leads to a good question: What happens if the property isn’t in the constructor’s prototype either? One possible answer is that the JavaScript engine gives up and says the property is undefined. But that’s not what happens.

Object

To understand what does happen, we need to understand the prototype chain. And to better understand that, let’s start with the root of all JavaScript objects: the Object constructor function. Being a function, it has a prototype property attached to it.

console.log(Object.prototype);

If you look through Object.prototype‘s properties, you’ll see a host of methods for doing things like converting the object to a string and so on.

Object.prototype
{…}
  __defineGetter__: function __defineGetter__()
  __defineSetter__: function __defineSetter__()
  __lookupGetter__: function __lookupGetter__()
  __lookupSetter__: function __lookupSetter__()
  __proto__: Getter & Setter
  constructor: function Object()
  hasOwnProperty: function hasOwnProperty()
  isPrototypeOf: function isPrototypeOf()
  propertyIsEnumerable: function propertyIsEnumerable()
  toLocaleString: function toLocaleString()
  toSource: function toSource()
  toString: function toString()
  valueOf: function valueOf()

When you create an object literal or instantiate an Object with new, that new object’s __proto__ will reference Object.prototype:

foo = {};
bar = new Object();

console.log( foo.__proto__ === Object.prototype ); // true
console.log( bar.__proto__ === Object.prototype ); // true

Given what we know, if we try to access a property that’s not in the object itself:

console.log( foo.toString() );

we expect that the JavaScript engine will first look in the object (foo) and if it’s not there, it will look in the object’s __proto__, which in this case is Object.prototype.

Since Object.prototype is itself an object, you might expect it to have a __proto__ property. It does:

console.log( Object.prototype.hasOwnProperty('__proto__') ); // true

but it’s value is null (not undefined!):

console.log( Object.prototype.__proto__ ); // null

Thus foo has a __proto__, and that _proto_ has a _proto_ as well:

console.log(foo.__proto__);  // same as Object.prototype
console.log(foo.__proto__.__proto__); // same as Object.prototype.__proto__
Prototype chain with object literal
Prototype chain with object literal

The prototype chain

Even though this isn’t a particularly long chain of __proto__s, it shows that there is a prototype chain associated with every JavaScript object.

Let’s take a deeper dive into prototype chains by looking at the chain that results when you instantiate an object with a constructor you’re written.

function Person(name) {
  this.name = name;
}
Person.prototype.greeting = 'Hello. My name is';
Person.prototype.sayName = function() {
  console.log(this.greeting, this.name);
};

var fuzz = new Person('Fuzz');

We expect that fuzz‘s __proto__ will be Person.prototype:

console.log( fuzz.__proto__ === Person.prototype )  // true

We also know that the constructor’s prototype is an object. Being an object, we might ask whether it also has a __proto__ property.

console.log( Person.prototype.__proto__ ); // same as
console.log( fuzz.__proto__.__proto__ );


Indeed it does. Person.prototype is an object — one that was constructed with the Object function. (Functions’ prototypes are constructed from Object() by default). So, we might expect that Person.prototype.proto refers to its constructor’s prototype, that is, Object.prototype. It does:

console.log( Person.prototype.__proto__ === Object.prototype );  // true, same as
console.log( fuzz.__proto__.__proto__ === Object.prototype );  // true


In the last statement above, we can see the prototype chain starts with an object’s __proto__ and ends not with Object.prototype but actually with Object.prototype.__proto__ (which is null).

console.log( Object.prototype.__proto__ );  // null (not undefined)

Let’s now have another look at:

console.log( fuzz.toString() );

The toString property isn’t actually defined in the Person constructor or its prototype. It’s actually defined in Object‘s prototype:

console.log( Object.prototype.hasOwnProperty('toString') ); // true

But clearly fuzz has no issues accessing toString. So here’s the full process: The JavaScript engine first looks in the object itself for a property. If it doesn’t find it there it looks in its __proto__ property. If it doesn’t find it there, it looks in __proto__‘s __proto__ … and up the chain until it finds what it’s looking for or __proto__ is null. If it goes so far up the chain that __proto__ is null, then the property is undefined.

This is the prototype chain in action.

Prototype chain with instantiated object
Prototype chain with instantiated object

Constructed versus factory generated objects

One last thing we should cover before wrapping this installment up is a subtle difference between objects you might make with constructor functions and those you might make with factory functions.

We have established that the __proto__ of an object instantiated with a constructor function is the prototype of the constructor. That’s just something JavaScript does automatically. However, when you create an object with a factory function using the pattern below:

function makePerson(name) {
  return {
    name: name,
    greeting: 'Hello. My name is',
    sayName: function () {
      console.log(this.greeting, this.name);
    }
  };
}

var fuzz = makePerson('Fuzz');

you are creating an object literal and returning a reference to that object. As we established above, the __proto__ of an object literal is Object.prototype. Thus fuzz.__proto__ in this example is Object.prototype, not makePerson.prototype.

This is a subtle difference that could land you in trouble if you forget.

In JavaScript, you should Never, ever mess around with Object. The chances that your tweaks and additions might accidentally clobber something now or in the future in that hugely critical function is too great. By extension this includes everything in Object.prototype.

In the example above, if you absentmindedly decided you wanted to stuff something common to Persons made with your factory function in the objects’ __proto__:

fuzz.__proto__.sayNameUnfriendly = function () {
  console.log("What's it to you?");
};

you will actually be dropping it into Object.prototype. This is bad bad bad! And it’s one reason to prefer constructor functions over simple factory functions.

Copyright © 2018 Mithat Konar. All rights reserved.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.