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 c
→ b
→ a
. 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.)
- Looks for
attr1
attribute within the objectc
. → Not found. - Looks for
__proto__
property within the objectc
. → Found. - Moves on to the object referenced by the
__proto__
property. → Moves to objectb
. - Looks for
attr1
attribute within the objectb
. → Not Found. - Looks for
__proto__
property within the objectb
→ Found. - Moves on to the object referenced by the
__proto__
property. → Moves to objecta
. - Looks for
attr1
attribute within the objecta
. → Found. - 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)
- Looks for
attr0
attribute within the objecta
. → Not found. - Looks for
__proto__
property within the objecta
. → Found. - Moves on ot the object referenced by the
__proto__
property. → Moves toObject.prototype
. - Looks for
attr0
attribute within the objectObject.prototype
. → Not Found. - Looks for
__proto__
property within the objectObject.prototype
. → Not Found. - 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 prototypep.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.