Be a pro with the prototype chain

In my previous article I talked at length about constructor functions, which are a way of creating objects in JavaScript. In order to invoke a constructor function, we add the new keyword before a function call, like so:

const person = new Person();

Putting the new keyword in front of any function changes that function from a function call to a construction call. It does 4 things (besides executing the function)

1.Creates a brand new empty object.
2.This empty object gets linked to it's prototype object.
3.The brand new object gets bound to the this object.
4. If the function doesn't return anything, the new object is returned.

This article is a deep-dive into the second step; the prototype object, which is JavaScript's way of sharing properties and methods across objects. Mastering this concept is essential for developers looking to write elegant, efficient and maintainable code. This article is based on the excellent PluralSight course called "Advanced JavaScript" by Kyle Simpson

In the beginning, there was Object.

Javascript has an inbuilt function called Object. This function has a property called prototype, which is an object containing properties and methods provided by the JavaScript environment; one such property on the prototype object is called constructor, which points to Object. In other words, the function Object has a reference to an object called prototype, which in turn has a reference back to the Object function via it's constructor property. If that sounds confusing, I've drawn a diagram that should help. The blue circle represents a function, and the green rectangle represents an object.

default_obj_prototype.svg

You'll note that one of the properties on the prototype object is __ proto __ which is actually a getter function that returns the prototype object of whatever the this binding is. If this is confusing, don't worry, we'll talk about this property in great detail later. As a side note, you'll probably agree that it's quite cumbersome to pronounce __ proto __ (i.e.underscore underscore proto underscore underscore), so the verbiage coined by the JS community for this prop is dunder proto.

Let's learn more about the prototype mechanism by analyzing the code below:

 
function Person(name) {  //line 1
  this.name = name; //line 2
}
Person.prototype.greet = function() { // line 3
  return "Hello, " + this.name; //line 4
}
const p1 = new Person("John"); // line 5
const p2 = new Person("Jane");  // line 6
p2.greetLouder = function() { //line 7
  return "HELLLOOO " + this.name; // line 8
};

console.log(p1.constructor === Person); //line 9 (outputs true) 
console.log(p1.prototype); // line 10 (outputs undefined)
console.log(p1.__proto__ === Person.prototype); // line 11 (outputs true)
console.log(p1.__proto__ === p2.__proto__); //line 12   (outputs true)

We have already learnt how lines 5 and 6 are executed:

1.A new object is created.
2. The object gets linked to Person's prototype object.
3. The object gets set to this.
4. The object is returned.

Lines 9-12 they seem pretty innocuous at first, but if you console.log(p1) here's what you would see: p1_consolelog.png

There is neither a constructor nor __ proto __ property on p1, and perhaps even more surprisingly, greet isn't a property on p1 either. So what's going on ?

Pro Prototypal Inheritance

When we try to access a property/method on an object, the runtime searches the scopes of that object to see if said property/method exist. If it doesn't, it traverses up the prototype chain of the object and sees if the property exists on the object's prototype, and if it's not found it will search that object's prototype and so on until the property is found or we've hit the end of the prototype chain. Keeping this in mind, when line 9 is executed the runtime basically says "hey, constructor isn't a property on p1, let me look at it's prototype object and see if the property exists there. Sure enough, constructor is a property on Person.prototype (it's one of the many inbuilt properties provided by default by the JavaScript environment). This process of an object inheriting properties/methods from other objects that are linked to it via the prototype chain is known as prototypal inheritance.

It's important to note that the prototype property is only available on the constructor functions, NOT on the instances themselves, which is why when we execute line 10:console.log(p1.prototype), the output is undefined. The instances get a reference to their constructor function's prototype object via an internal property called [[Prototype]]. You can't access this property directly, which is why it's grayed out in console.log(p1) image above but when the runtime traverses up an object's prototype chain, it is via this property that it's traversing. So when line 11 is executed, the runtime will not find __ proto __ on p1, so it will traverse up it's protoype chain (via the [[Prototype]] prop) to Person.prototype. As you'll soon learn, __ proto __ is also NOT a property on Person.Prototype, so then the runtime will go up Person.prototype's chain to Object.Prototype. Turns out there's a property __ proto __ on Object.Prototype, which is basically a getter function which returns the prototype of whatever the this binding is (I mentioned this earlier!). Well, the this binding of p1 is Person, so the runtime will return Person's prototype object.

If the above seems a little confusing, that's because it is! To solidify the concept, I've drawn a diagram which shows the prototype chain linkage from p1 all the way up to Object.prototype. As a reminder, objects are green squares and functions are blue circles.

person_prototpe_link_final.svg

It'll be instructive if you read the diagram from bottom to the top. p1 only has the name property, whereas p2 has the name property and the greetLouder method. The greet method is only on Person's prototype object, but it's accessible to all objects constructed by the the Person constructor function. The Person's prototype object only contains the greet method, all other methods that we've seen (such as __ proto __) are delegated up it's prototype chain to Object.prototype

There are two major benefits of delegating function calls up the prototype chain:

  1. We save a tremendous amount of memory by not copying over functions and properties unless we really need to. Imagine that we constructed a thousand variations of Person, and that there were a hundred methods that were defined inside of the constructor function like so:
function Person(name) {
   this.name = name;
   this.greet = function() {
      return "Hello ," + this.name;
   }
  //100 more methods
};
 
var p1 = new Person("John");
/// 1000 more Person objects  

The JavaScript thread will need to allocate memory for a hundred thousand functions as opposed to only a hundred functions when prototypal inheritance is used.

  1. We can consolidate logic in one place, making the code more maintainable. Folks who're coming to JavaScript from an object oriented language are especially partial to overriding/extending methods that are defined in the parent class. Let me explain using code:
public class Dog {
  //mark the method as 'virtual' so the compiler knows it can be overwritten       
  public virtual void Greet() { 
    Console.WriteLine("Woof woof!");
   }
}

//ExcitedDog extends Dog
public class EnergeticDog : Dog {
  public override Greet() {
    WagTail();  //add additional functionality before calling the base method;
     base.Greet();
   }
}

It's one of the fundamental dictums of object oriented languages to create parent/child classes and have the methods in the child class override the base methods in the parent class, in JavaScript the equivalent of base.Greet() as shown in the example above is : Dog.prototype.greet.bind(this)();. Look at the code below:

function Dog() {
   //add props here
}

Dog.prototype.greet = function() {
    console.log("Woof woof!");
}

var excitedDog = new Dog();
excitedDog.wagTail = function () {
   console.log("waggin my tail");
}
excitedDog.greet = function() {
    this.wagTail();
    Dog.prototype.greet.bind(this)(); //<--allows us to invoke the greet method defined on the prototype! 
}
excitedDog.greet(); // outputs "waggin my tail", then outputs "Woof woof!";

As a side note, I personally think there isn't much benefit to be gained from pretending JavaScript is an object oriented(OO) language or shoe-horning OO paradigms in JS; I'm hoping that through understanding the prototypal inheritance we've gained a deep appreciation for the beauty and elegance of JS's underlying mechanism.

Prototypal Inheritance: Not All Pros

We've looked at how powerful a mechanism prototypical inheritance can be; but it's not without flaws. A major drawback of prototypical inheritance is that the __ proto __ property is NOT readonly, i.e. it can be changed.

For example, look at the following code:

function Person(name) {
 this.name = name;
}
Person.prototype.greet = function() {
  return "Hello ," + this.name;
}
var p1 = new Person("John");

p1.__proto__ = null; //the __proto__ is NOT a readonly prop and can be changed at will
p1.greet(); //Uncaught TypeError: p1.greet is not a function

Here we see that all the benefits of prototypical inheritance can be destroyed in one fell swoop at the hands of a careless(or malicious) developer. Indeed, one of the reasons that the __ proto __ property has double underscores preceding and succeeding it and is not simply called something like superProto is to reduce the probability of it being overwritten.

Still Pro Prototype

Prototypal inheritance is one of the most powerful and commonly used patterns in JavaScript. I hope that this article was helpful in demystifying the more confusing aspects that underlie it. In future articles we'll explore design patterns that employ prototypal inheritance, so stay tuned!