Hello, Hashnode people!
I am going to talk about one of the Programming Paradigms, Object-Oriented Programming, but, more precisely, Prototypal Inheritance.
If you are an intermediate JavaScript developer or want to learn further without much programming background knowledge, I hope my article can be a good start for your programming journey.
First of all, have you heard about Object-Oriented Programming?
Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods).
A feature of objects is that an object's own procedures can access and often modify the data fields of itself (objects have a notion of "this" or "self"). In OOP, computer programs are designed by making them out of objects that interact with one another. OOP languages are diverse, but the most popular ones are class-based, meaning that objects are instances of classes, which also determine their types.
from wikipedia
However, unlike other OOP languages, JavaScript is not a class-based language (in ES2015, there is a new class
keyword, but, this is just a syntactical sugar), but, a prototype-based language.
When we write codes in JavaScript, all the functions we create or the built-in methods and properties are intrinsically objects. Almost everything is an object, even null
is an object in JavaScript!
The methods and the properties of all the built-in objects are created by prototypal inheritance Under the hood.
Prototype Chains
There are many built-in objects in JavaScript.
I'd like to talk about the most common objects, Array. Before that, open a dev tool console and create an empty Array []
there and then click the newly created Array instance. The instance is created by Array constructor function which is a high-level global object.
Back to the console, you can see __proto__(called dunder, double under) proto
which means that it is an instance created by a constructor function object, here Array
.
Remember! When you create a constructor function just like native ones, the function name should start with a capital letter. It is a naming convention so, all other developers would know it instantly. It is bad practice if you create one with a lower case just like a regular function, but, it would still work the same as a constructor function. How confusing! When you create an instance of a constructor function, use the
new
keyword like below.
function Animal(type, name){
this.type = type;
this.name = name;
}
const myPet = new Animal('Dog', 'Milo');
A constructor object has a prototype property which contains its own constructor function which is itself and other method functions. Can you see all the methods of the global object Array
below? concat
, entries
, filter
, map
and so on. All of these methods (functions) are all linked to the Array
constructor object by the prototype object.
Whenever we create a new instance of a constructor function object, the instance is secretly linked to its constructor's prototype and we call this prototype chain
. The instance has __proto__
property which tells us which constructor's instance it is. Basically, Prototype Chain
is a mechanism to keep looking up a property until it finds one, starting from its own property and then, constructor and then constructor's constructor and so on.
// Array.prototype.map
const arr = [1, 2, 3, 4, 5];
arr.map()
Like the above example, Array arr
can use the map method thanks to the property chain. arr
is an instance of Array constructor which is initialised by Array literal. I hope you have solidified the concept of the prototype chain, a constructor function and an instance by now.
Prototypal Inheritance
Ok, take a deep breath and we will finally start with Prototypal Inheritance
.
If you look at the codes above, this one has a few pitfalls as the codebase gets configurable and the constructor function could be used for multiple instances.
Imagine that you want to extend methods of the Animal constructor to add some new features for all instances. You want to give an age property for each animal instance and also want to increment it individually. So, you write codes below.
Animal.age = 0;
Animal.getOlder = function(){
return this.age++;
}
In this way, we cannot leverage the code reusability. As the Animal
constructor function only holds the getOlder
method and the age
property, those cannot be inherited to the milo
instance I created previously. The property and the method are not linked to the instance. if you try to initialise the age property as you may expect like milo.age = 0
, it will says undefined
. You eventually have to create the individual property and method for each instance if you want to make it work. A couple of instances wouldn't be a pain. However, what if you have hundreds of them? Can you imagine how many repetitive codes you have to write and the amount of work you have to take care of?
This is where the power of prototypal inheritance comes into play. It helps us save memory and keep codes DRY(Don't Repeat Yourself, a programming principle).
As JavaScript is a multi-paradigm language, this is just one of the techniques used in order to write the codes in a more efficient way using prototypal delegation
. Each constructor is just an inherent object and its properties and methods(functions) can be linked and shared by the prototype
object throughout all the instances inherited from it.
I strongly recommend you actually engage in coding while reading this at the same time. Thank you for reading and I will come back with more content for beginners/intermediates who want to learn it in-depth!