Inheritance is a fundamental concept in programming languages. However, it is implemented differently in Object-Oriented Languages such as Java and Javascript.
I have seen developers who move from OOP languages such as Java struggling to understand the JS Inheritance consequently end up not using it altogether which can also lead to inefficient code.
In this post, I will try to explain the JS Inheritance also called Prototypal Inheritence especially for those who come from OOP languages.
Background
Let's quickly define Inheritance first. In simplest terms, it is a mechanism by which one object can acquire properties and behaviors or other objects. This is one of the main tenets of most common Object-Oriented Programming (OOP) languages and promotes reuse. This represents an IS-A or parent-child relationship. OOP languages implement what is called Class Inheritance.
Javascript also allows Inheritance but it is implemented entirely differently from OOP languages and is called Prototypical Inheritance. For OOPs languages such as Java has the concept of Classes and Instances and they are separate. Instances (objects or entities) are created from Classes (generalization or blueprints/definition) but in Javascript, there is no concept of class - you don't define a class and then create instances from them.
Note that similar to Java language Javascript does not support multiple inheritances - the object has only one prototype and therefore can only inherit one object. Note that Java does support inheritance of more than than one interface.
Prototypal Inheritance
Before we talk about Inheritance in Javascript we need to understand what is a Prototype.
All objects in JS can have properties that you define. For example, here object obj1 has two properties, foo, and bar
function Function1() {
this.foo = 'value1',
this.bar = 'value2'
}
In addition, all functions have one additional hidden property called prototype which by default is an empty object. Using this hidden property you can extend ALL objects created using function constructor (using the new keyword) and define new methods or properties.
var obj1 = new Function1();
Function1.prototype.baz = 'value3';
Function1.prototype.getProps = function() {
return this.foo + ', ' + this.bar + ', ' + this.baz;
}
console.log(obj1.baz)
console.log(obj1.getProps())
Output
value3
value1, value2, value3
In the above example, we used the hidden prototype property of the object and enhanced or extended it by adding one property and a new method. Internally the prototype is implemented as __proto__ property and even though you can access it directly it should not be used directly.
console.log(obj1.__proto__)
Function1 { baz: 'value3', getProps: [Function] }
The prototype object can also further have a __proto__ object. This is called the prototype chain. You can have another object that points to the same prototype as defined for the first object. During execution, the javascript engine starts at the top of the chain and works its way down till it finds the property in the prototype chain. The bottom of the prototype chain is Object{}. This mechanism of looking though the prototype chain is also called Delegate Prototype.
The example above uses Method Constructors and is considered an old way.
Note that EC6 introduced the class keyword to Javascript language which you can use to create an object. Note that even though it looks like Classes in OOP languages such as Java, it is not the same. class in Javascript is a type of function and uses class instead of function keyword and also has a constructor method to initialize class properties based on the passed parameters. The only difference between Function constructor and using class forces you to use new keyword otherwise you will get an error.
The new and preferred way (supported by newer browsers) is to use Object.create passing a prototype object and using dynamic linking to setup properties on the object. This is also referred to as Pure Prototypal Inheritance. You don't need to use class, extends, constructor, or super.
var User = {
firstname: '',
lastname: '',
getName: function() {return this.firstname + ' ' + this.lastname},
}
var Member = Object.create(User);
Member.registrationDate = new Date();
var John = Object.create(Member);
John.firstname = 'John';
John.lastname = 'Doe'
John.registrationDate = new Date(2020, 7, 23, 10, 33, 30, 0);
console.log(John)
console.log(John.__proto__)
console.log(John.__proto__.__proto__)
console.log(John.getName())
console.log(John.__proto__.__proto__.__proto__)
Output
{
firstname: 'John',
lastname: 'Doe',
registrationDate: 2020-08-23T17:33:30.000Z
}
{ registrationDate: 2020-08-24T02:15:02.161Z }
{ firstname: '', lastname: '', getName: [Function: getName] }
John Doe
{}
Let's walk through the code and try to understand what's going on here.
First, We created a User object (initialized to default empty values for first and last names) using object literal syntax. Then extended the User object using Object.create and added a new property registrationDate set to the current date by default. Then created one user object John and passed Member object and overridden the default values.
The first console log for John shows all the properties of John.
The next two console logs show prototype chains. The first one pointing to Member object and the prototype of that points to User.
To resolve John.getName() JS execution context traverses the chain all the way to base object (User) and find the getName method and displays the first and last name.
Finally, the console log statement logs the prototype of the base object which is an empty object {}.
The key thing to understand here is that even though you did not explicitly set the getName method on John object using prototypal inheritance it was able to access it. Note that if you now add a new method to any of the prototypal chain all the objects will inherit this new method.
Finally, you can use prototypal inheritance to extend or override the behavior of built-in function constructors. For example, refer to this article.
Besides code reuse are there any other benefits of Prototypal Inheritance
Yes. The properties and methods in function constructor occupy memory. If you are instantiating a very large number of objects the attributes will be duplicated across all objects. While you do need attributes defined in the base object store the state variables there is no need to have functions methods that operate on object attributes to be duplicated across all objects.
Using prototypal inheritance you can extend the behavior of all objects by defining them using prototypal inheritance thereby reducing the memory pressures.
No comments:
Post a Comment