2. Prototypal Inheritance
Without going too deep and covering all aspects of it, this article should answer following questions:
How inheritance works
Why it is an important concept in JavaScript and
Why it is useful for us to understand it
Prototypal Inheritance
This kind of inheritance is based on the prototype chain. A prototype
object is just that: a regular object. This is way it's necessary to have some kind of mechanism that makes it possible for created objects to inherit methods and properties.
We have already mentioned this mechanism briefly in our Prototypes article. It is generally called internal prototype (historically _proto_ but the use of it has been deprecated).
How can this concept be helpful in JavaScript? Say, we have three objects A, B and C connected by prototypal inheritance. In case that object B has a property that object A needs but doesn't have, object A can access this property as if it was its own. The same applies if object B needs a property that object C has, but B doesn't. Equally, the object A can have access to properties of object C.
Here we can see the importance of this concept, it makes possible for code to be shared.
Let's Start with Examples
To implement such kind of inheritance hierarchy we are going to create three constructor functions:
function Device() {
this.category = "Device";
this.subcategory = "Electronics";
this.getPath = function () {
return this.category + "/" + this.subcategory;
};
}
function Usb() {
this.name = "USB Memory Stick";
}
function Mouse(connectivity, dpi) {
this.name = "Computer Mouse";
this.connectivity = connectivity;
this.dpi = dpi;
this.getMouseInfo = function () {
return `A ${this.connectivity} ${this.name} with maximum DPI of ${this.dpi} `;
};
}
A code where this inheritance "magic" happens is following:
Usb.prototype = new Device();
Mouse.prototype = new Usb();
What happened here? We swapped the initial prototype
object of the Usb()
constructor with an instance of Device()
constructor. Then we did the same with the prototype
of Mouse()
which we replaced with an instance of Usb()
constructor.
console.log(Usb.prototype)
What we see in the image above is that prototype
object of Usb()
constructor now contains all direct properties of the Device()
instance: category
,subcategory
and getPath
.
We can also recognize that internal <prototype>
object points to the prototype
object of Device()
constructor which is empty.
console.log(Mouse.prototype)
As for prototype
object of Mouse()
constructor, we can see that it contains a direct property of the Usb()
instance (name
). The internal <prototype>
points to the prototype
object of the Usb()
constructor.
These properties and methods are not inherited from Device()
constructor but from its instance. We can now modify or even delete the Device()
constructor which will have no effect on the Usb()
. All we need is one instance from which features will be inherited.
Side Effect of Replacing Prototype Object
When we replace prototype
object, instead of just adding methods and properties to it, we get side effect for the constructor
!
console.log(Usb.prototype.constructor); // Device()
console.log(Mouse.prototype.constructor); // Device()
After inheritance process it's a good idea to again set constructor
to default:
Usb.prototype.constructor = Usb;
Mouse.prototype.constructor = Mouse;
console.log(Usb.prototype.constructor); // Usb()
console.log(Mouse.prototype.constructor); // Mouse()
Let's Analyze the Inheritance Process
We're going to create an instance of Mouse()
and call the getMouseInfo()
method.
const mouseOne = new Mouse("wired", 4800)
console.log(mouseOne.getMouseInfo()) // A wired Computer Mouse with maximum DPI of 4800
This was expected behavior as the getMouseInfo()
is direct method of the mouseOne
object. Now, the mouseOne
object doesn't have its own getPath()
method but it will inherit it and use it as its own.
Also important to notice, is that instances of Mouse()
have own name
property. But they also inherited name
property when the prototype
object of Mouse()
was swapped with an instance of Usb()
.
And we can clearly see what name
value instances of Mouse()
will have. The one from their own name
property!
console.log(mouseOne.getPath()) // Device/Electronics
When calling mouseOne.getPath()
JavaScript Engine does the following:
looks up own properties of
mouseOne
and doesn't find thegetPath()
thereidentifies the internal
<prototype>
of themouseOne
(because keywordthis
refers to themouseOne
)this internal
<prototype>
points directly to theprototype
object of itsMouse()
constructor but JavaScript doesn't find the method there either and thus it has to keep looking.the
prototype
object of theMouse()
constructor also contains internal<prototype>
object. Now, this internal<prototype>
points to theprototype
object of theUsb()
constructor.Finally, JavaScript Engine finds the
getPath()
method and this method is called as if it was own method ofmouseOne
.
Follow Internal Prototypes up to the Object()
Now, because every object in JavaScript has internal <prototype>
by following these internal <prototype>
objects we can reach the prototype
object of the Object()
constructor.
Looking at Object()
we can conclude that when we create an object that object will inherit some methods: toString()
, hasOwnProperty()
and others.
Why to put your time and effort in understanding this mechanism?
This inheritance mechanism is not easy to understand. For the ones who are just starting learning JavaScript I would say it's not that important. On the other hand for the intermediate learners and the ones using frameworks/libraries I'd say it's important or at least useful. From my point of view, the reasons are following:
Whenever we use Inspect tool to open Console we'll see all this
<prototype>
objects. It helps to understand their purpose in order not to get confused by them.The same applies when working with frameworks because prototyple inheritance is widely used in JavaScript frameworks and libraries and it's useful to know how all these different methods and properties are inherited.
Resources
- Object Oriented JavaScript - third edition by Ved Antani and Stoyan Stefanov
Disclaimer
By no means am I a JavaScript expert, so feel free to comment and point to the potential mistakes. I write articles as my learning experience and with hope it can be useful for others. Any feedback is welcome!
Subscribe to my newsletter
Read articles from Amer directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by