JavaScript’s Prototype Chain Made Easy

TOAST UI
13 min readJul 5, 2019

--

JavaScript is classified as a multi-paradigm language due to the fact that it is object-oriented while treating functions as first-class objects, thereby allowing functional programming. If you have any experience really studying JavaScript, you are probably already familiar with the fact that JavaScript does not have an actual class. This lack of class makes object creation and inheritance in JavaScript a little bit unique compared to other OOP languages. JavaScript’s OOP operates with a mechanism known as the prototype. Since prototype is a piece of information that is pivotal to truly understanding JavaScript, so it comes with no surprise that there are numerous articles online trying to explain it. In this article, I will mainly focus on the prototype chain, the core mechanism that enables the inheritance system in JavaScript.

Object

There are two ways to create objects in JavaScript: using the object literal and the constructor.

var objectMadeByLiteral = {};var objectMadeByConstructor = new Object();

Since object literal can be seen as a shortcut for creating an Object and the constructor in the second line of code also creates an Object, the result of the two methods are identical in terms of the content of the Object and the structure of the prototype. Because both are instances of the Object type, both Objects have access to methods like hasOwnProperty, toString, and valueOf.

The Object type is the highest ranking type of all objects. In other Object-Oriented languages, the code above has merely created an instance that has the Object type, so conceptually, it cannot be viewed as having inherited from the Object. However, in JavaScript, you have to view the inheritnace somewhat differently. While it is true that the object we created is just an instance with type Object, in JavaScript, where prototypal inheritance is allowed, it is more accurate to say that the objects we created have inherited the prototype of Object constructor.

To be frank, the word inheritance is only used to discuss the idea in terms of OOP, and in fact, it works like a linked-list connecting different objects that allows for a much more dynamic structure than the class mechanism. I’m not outright stating that such dynamic connections are better. Since the structural changes of the inheritance practically have no restrictions, developers need to have a solid understanding of conventions and anti-patterns to refrain from unleashing the chaos of misusing such features.

Prototype

You can use the prototype to connect two different objects and form one-way inheritance hierarchy. In JavaScript, such inheritance of connecting two objects can also be seen as connecting multiple objects to delegate the member functions and member variables. JavaScript uses this characteristic to simulate the inheritance in other languages.

var a = {
attr1: 1
}
var b = {
attr2: 2
}

Let’s say that there are two objects a and b. The object a has access to attr1 attribute, and the object b has access to attr2. As of now, the object b does not have any access to attr1 through object a. If you wanted to use object a’s attr1 in object b as if it has been declared in b, you can use the a special property known as the __proto__ to connect b to a.

var a = {
attr1: 'a1'
}
var b = {
attr2: 'a2'
}
b.__proto__ = a;b.attr1 // 'a1'

JavaScript engine treats objects like a hashmap with key-value pairs. You can put data and functions in the place of the value, as well as storing random values that may be necessary for the internal engine. The most critical feature of the prototype chain is the __proto__ property used by the engine. The __proto__ is actually the result of the ECMAScript’s [[Prototype]] spec being exposed to JavaScript, but it is more of a legacy of the bygone times. While most modern browsers allow developers to inspect __proto__ for the sake of debugging, attempting to directly access and manipulate it should be avoided. For the purpose of this article, I will only use it to facilitate a clear understanding. If you have to check any objects that reference the __proto__ (for example, if you are building a framework,) do not directly access the __proto__ property, but use the Object.getPrototypeOf().

In the example code, the __proto__ property of object b points to (references) the object a. In other words, the object b has access to the member variables and methods as if they are declared in the object b with minimal restrictions. This process results in a system that is similar to inheritance. In other programming languages, when inheriting from a class, it uses the inheritance information defined in the class to create a new object that follows the structure laid out in inheritance. Contrarily, the inheritance system based on the prototype is executed using a dynamic connection between the objects. Therefore, it allows developers to change and add the content of the inheritance or even change the entire inheritance structure even if the object has already been created. However, it should be noted that most of what I have described here are bad practices, and should be avoided.

var a = {
attr1: 'a1'
}
var b = {
attr2: 'a2'
}
b.__proto__ = a;b.attr1 // 'a1'a.attr1 = 'a000'; // Changing the content of an inherited objectb.attr1 // 'a000'a.attr3 = 'a3' // Adding to the content of an inherited object
b.attr3 // 'a3'
delete a.attr1 // Deleting a content of an inherited object.
b.attr1 // undefined

Such manipulation is only possible in prototypal inheritance. Regardless of whether prototypal inheritance is better or worse than the class inheritance, in JavaScript, only prototypal inheritance is allowed. In actuality, while simulating a class inheritance using the prototypal inheritance may be easy, simulating the prototypal inheritance using class inheritance is extremely difficult. With everything said and done, such one-way delegation relationship between two objects is called the prototype chain. If you have been attentively, you have already understood more than 90% of the prototype chain.

The trick is to form these connections in our codes directly without using the __proto__ property, and there are a few known ways to do exactly that. However, before we start manipulating the prototype, let’s learn a little more about searching for properties using prototypes.

Property Lookups Using Prototypes

In JavaScript, there are two ways to lookup a property: the prototype lookup and the scope lookup. This article will only discuss using prototypes to search for the properties. The process of prototype lookup is one of the key defining feature that differentiates prototypal inheritance from the class inheritance. To put it simply, when you create an object using class inheritance, you already know which members the object has access to through the inheritance structure. However, in the case of prototypal inheritance, you would need to run the object in order to figure out to what kinds of members the object has access.

Of course, you as the developer should know such information without having to run the code, but from the perspective of the JavaScript engine, it dynamically searches for a method every time the method is executed. Therefore, because the content at the point of execution matters more than when the object is created, the content of inheritance for the object which has already been created is subjected to change. This process of using the prototype chain to search for a specific method or property of an object is called the prototype lookup.

var a = {
attr1: 'a'
};
var b = {
__proto__: a,
attr2: 'b'
};
var c = {
__proto__: b,
attr3: 'c'
};
c.attr1 // 'a'

In the code above, we have created three objects and connected the three objects using each object’s __proto__ property so that cba. If you were to try and access the attr1 attribute from object c by using c.attr1, since attr1 is not directly declared in c, the JavaScript engine goes through these steps. (Of course, the number of steps can be minimized by engine optimization.)

  1. Looks for attr1 attribute within the object c. → Not found.
  2. Looks for __proto__ property within the object c. → Found.
  3. Moves on to the object referenced by the __proto__ property. → Moves to object b.
  4. Looks for attr1 attribute within the object b. → Not Found.
  5. Looks for __proto__ property within the object b → Found.
  6. Moves on to the object referenced by the __proto__ property. → Moves to object a.
  7. Looks for attr1 attribute within the object a. → Found.
  8. Returns the value of attr1.

To put it in simpler terms, the JavaScript engine follows the connections made with __proto__ as if it were traversing a linked-list to find the desired key value. If you were to search for a non-existent attr0, the JavaScript engine would follow different steps starting from step 7.

(From Step 7)

  1. Looks for attr0 attribute within the object a. → Not found.
  2. Looks for __proto__ property within the object a. → Found.
  3. Moves on ot the object referenced by the __proto__ property. → Moves to Object.prototype.
  4. Looks for attr0 attribute within the object Object.prototype. → Not Found.
  5. Looks for __proto__ property within the object Object.prototype. → Not Found.
  6. Returns undefined.

At the end of every prototype chain is the Object.prototype. Therefore, Object.prototype does not have a __proto__ property. Since attr0 attribute does not exist in the last stage of the prototype chain, Object.prototype, and the Object.prototype does not have a __proto__property, the engine stops the search, and returns the undefined. The V8, JavaScript engine for Chrome and Node.js, optimized this process to reduce the cost of search and enhanced the performance. Because the prototype chain resembles a one-way linked-list, we can enforce the idea of inheritance on JavaScript. This means that you can access the members defined in a from c, but a does not have access to members defined in c.

var a = {
attr1: 'a'
};
var b = {
__proto__: a,
attr2: 'b'
};
var c = {
__proto__: b,
attr3: 'c'
};
a.attr3 // undefined

We can use this characteristic to implement a method override.

var a = {
method1: function() { return 'a1' }
};
var b = {
__proto__: a,
method1: function() { return 'b1' }
};
var c = {
__proto__: b,
method3: function() { return 'c3' }
};
a.method1() // 'a1'
c.method1() // 'b1'

When we call the method1() from object c, the JavaScript engine uses the prototype lookup to move from object c to object b. Since the method that we are looking for already exists in b, the engine does not have to look all the way up to a to run the method1(). This kind of situation can be referred to as object b overriding the object a’s method1(). Now, let’s explore using the prototype to create objects.

Constructor

Once we use the constructor function to create an object, the object created is automatically connected to the prototype object of the constructor by a prototype chain. I will assume that you are already familiar with the basics of constructors, and will only explain what is absolutely necessary in understanding the prototype chain.

//constructor
function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function() {
return this.name;
};
var p = new Parent('myName');

Let’s look at the short example we have above. The object p created using the constructor Parent has access to the name attribute as well as the getName method defined in the prototype. The reason object p has access to methods defined in prototype of the Parent is that the __proto__property of object p points to the Parent.prototype. This process happens automatically within the engine when the constructor is called with a new keyword. Behind the scenes, the engine follows these steps. (The code below is written to explicitly show how the engine functions.)

var p = new Parent('myName');// Happening behind the scenes
p = {}; // The engine creates a new object
Parent.call(p, 'myName'); // The engine uses the call to substitute the 'this' of the Parent function to p to run the code
p.__proto__ = Parent.prototype; // Connects the prototype
p.getName(); // 'myName'

The code I have written above basically shows how the engine connects the object created using the constructor to the prototype of the constructor. Similarly, you can infer the steps the engine takes to perform a prototype lookup of p → Parent.prototype. However, so far, the code does not specifically mention any purposeful inheritance aside from the Object type. Then, how do we create a Child type that inherits from the Parent type?

The structure of the prototype chain should be Instance of a Child → Child.prototype → Parent.prototype, and connecting the instance of a Child → Child.prototype is extremely similar to what we did with the Parent above. The key task, now, is connecting the Child.prototype with the Parent.prototype. This portion is the trickiest to understand the prototype, but once you understand it, you will be able easily create multiple leveled inheritance hierarchies.

Object.create();

Connecting the prototype of a Child to the prototype of a Parent is nothing more than connecting two different objects together. There are two ways in doing so, and the first method is just an old-fashioned cheap trick, while the new and second method is making use of the standard API. You may be wondering why people opt to use a trick when there is a standard API, and the answer to that is because the standard API lacks browser support (supports IE9 and above.) In certain cases, it may be necessary to consider the IE8, so I will first explain using the standard API and then using the old way.

The standard API that I am talking about is the Object.create(). The Object.create() takes an object as an input, and returns a new object that is connected to the input object by the prototype chain. Let’s go back to the __proto__ example from the beginning.

var a = {
attr1: 'a'
};
var b = {
__proto__: a,
attr2: 'b'
};

I have written the code above to explain the process of connecting the prototypes through the use of __proto__. It should never be used in real-life programming situations. The problem with the code is that the I directly accessed the __proto__ property, but I can connect the prototype chain without using the __proto__ property by using the Object.create().

var a = {
attr1: 'a'
};
var b = Object.create(a);b.attr2 = 'b';

By using Object.create(), a new and empty object in which the __proto__ refers to the object a, and the newly created object is stored in b. The object b can now access members of object a by b.attr1. However, I admit that what I have done above can be implemented without Object.create().

Now, for the trick. The standard APIs like Object.create() are created by observing and building on the trick I am about to show you.

var a = {
attr1: 'a'
};
function Ghost() {}
Ghost.prototype = a;
var b = new Ghost();b.attr2 = 'b';

The Object.create() does exactly the same procedure as the code I have just shown you. We create a disposable (or temporary) constructor called Ghost so that the prototype refers to a. Then we create an instance of Ghost in which the __proto__ refers to a. Aside from the fact that the object b is an instance of Ghost, it is no different from the object we created using Object.create(). We can even hide the fact that b is an instance of Ghost by changing the reference of b.constructor to Object, but it is simply better to use Object.create(). It is understandable if you are still unsure that the whether Child prototype and Parent prototype have been connected yet. Now, let’s explicitly connect the two prototypes.

function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function() {
return this.name;
};
function Child(name) {
Parent.call(this, name);
this.age = 0;
}
Child.prototype = Object.create(Parent.prototype); // (1)
Child.prototype.constructor = Child;
Child.prototype.getAge = function() {
return this.age;
};
var c = new Child(); // (2)

At (1), we use the Object.create() to change the prototype object of the Child. Then, the new object, Child.prototype is structured so that the __proto__ property points to the Parent.prototype, and at (2), __proto__ of c refers to the Child.prototype. Now, we have successfully created a prototype with the structure of c → Child.prototype → Parent.prototype, and if we were to perform a prototype lookup, the engine performs a property search according to our designated path. Additionally, extending the Child constructor by borrowing the Parent constructor is an old technique, and prior to ES6, it was the only way to execute the constructor of the parent in JavaScript. This way, we inherit everything in the constructor of the Parent type.

In environment that does not allow for the Object.create(), we must resort to using the trick we discussed earlier.

function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function() {
return this.name;
};
function Child(name) {
Parent.call(this, name);
this.age = 0;
}
// diff start
function Ghost() {};
Ghost.prototype = Parent.prototype;
Child.prototype = new Ghost();
// diff end
Child.prototype.constructor = Child;
Child.prototype.getAge = function() {
return this.age;
};
var c = new Child();

I have put comments around the sections that were changed to make it easier for you to see. You may be thinking that, just judging from the code, the use of the temporary Ghost constructor is unnecessary if you use the Parent constructor in its place. However, such method is impractical because of the properties created by the constructor. In other words, if you were to use the Parent constructor in its place, the object created using the Parent constructor is also given the name attribute. Using this object as the prototype of the Child forces the instances of Child to share the name attribute, but because the members created using the constructor cannot be made to share but to belong to individual instances, it is bad practice. Therefore, we simply borrrowed the Parent constructor from inside of the Child constructor to declare the name attribute in each instance. To summarize, we use temporary constructors like Ghost gain access to an empty object that is purely connected to the prototype chain.

ES6

While the idea of inheritance in JavaScript may be simple, the process itself is considerably lengthy. The process remains lengthy even if we use Object.create(), but the class spec has been added in ECMAScript6 to fix this problem. Although the class is new, it is not new conceptually, and it can be seen as a shortcut to the verbose process of implementing the inheritance. Using the ES6’s class, our previous examples of Parent and Child can be written as such.

class Parent {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
class Child extends Parent {
constructor(name) {
super(name); // Instead of borrowing a constructor, use ...super function.
this.age = 0;
}
getAge() {
return this.age;
}
}

It is clear that the code has become much more concise and more readable. While the structure of the code has changed, the underlying logic and the connection and structure of the prototype chain remains identical, and the prototype lookup functions exactly the same way.

Closing Remarks

So far, we have explored what the prototype chain looks like, how the prototype lookup searches for a property, and how to create prototype chains. I have written this article with the hopes that this article illuminates any uncertainties you may have had while understanding the prototype chain. The class discussed at the end is a newly introduced spec in ES6, and transpiling the code using Babel can solve the cross-browsing issue.

--

--

TOAST UI
TOAST UI

No responses yet