Prototypical inheritance

Prototypical inheritance

Introduction

Prototypal inheritance (eng. prototypal inheritance) is a type of inheritance where an object inherits properties directly from another object (not from a template, ie class). There are two basic mechanisms that enable prototypical inheritance:

  1. Concatenative inheritance
    This process is based on directly copying the properties of one or more objects to a new object. It should be noted that in order to copy a property, it is necessary that the property’s enumerable descriptor be set to true. This mechanism is also accepted with the ES2015 standard, with the help of the “Object.assign()” method.
  2. Prototype delegation
    This process is based on the principle that properties and methods are inherited through a “chain of related prototype objects” (eng. “prototype chain”).

NOTE:
In the framework of the ES2015 standard, a syntax for creating objects via classes is presented, however, this is only an aesthetic change, because in the background, all work related to inheritance is performed according to the previously mentioned mechanism “prototype delegation”.

Prototypical delegation

Definition

function object

Prototypal delegation is the most important object inheritance mechanism in JavaScript. It is based on the principle that properties and methods are inherited through a “chain of connected prototype objects” (eng. “prototype chain”).
The Object() function is the “mother” of all objects, which through its “Object.prototype” object passes some basic properties and methods to all newly generated objects. Read more about this property of the constructor function Object() in the article Object() & Object.prototype
Each new object also has its own “prototype” object which is empty at first, but is connected via a “inheritance chain” to the parent object, its parent object and so on until Object.Prototype of the object. An inheritance chain is actually a set of related prototype objects that ends with Object.prototype.
In order for an object to be able to pass a method to its successor, it is necessary to define the method in its prototype object, because that way it is accessible to other objects that will be created based on it.

EXPLANATION:
When the JavaScript engine looks for a method, it first checks if we have defined it within our own prototype objects. If it does not find it there, then it looks for it in the prototype object of the parent. But if it does not find it there, it will search further along the chain of interconnected prototype objects until it reaches the last one in the chain “Objec.prototype”. If it doesn’t find it in it either, it throws an error.

Why use prototypical delegation?

The reason for using prototypical delegation as the main method of inheritance is good resource optimization, and this is best explained by comparing it with a non-optimizing mechanism. First, we’ll generate objects based on the constructor f only, using the new keyword. Objects generated in this way get a copy of the properties and methods found in theconstructor function at the moment of generation.

The newly generated object (myCar) has received a copy of all properties. Therefore, we should pay attention to the fact that if after generating the object we change some property in the constructor function, it will not affect the instantiated object, because they are not connected:

The bad side of this approach comes to light when a large number of new objects are instantiated based on this constructor function. The reason for this is the fact that when new objects are instantiated in each object, methods are also instantiated, so this approach unnecessarily consumes resources for instantiating the same method in each object.
To obtain more optimized code, the mechanism “constructor function + prototype inheritance” is recommended. In this mechanism, the methods needed by all new objects are defined in the “prototype object“. Such methods placed in the prototype object are instantiated only once within that parent object, while they are delegated to other objects (generated on the basis of the parent) along the chain of inheritance. The properties are specific for each object, so we define them in the constructor function, because that way each new object gets its own copy with specific values.

After generating a new object (“myCar”), the showMessage() method is available to it, which is instantiated only in the parent prototype object.

NOTE:
Inheritance via prototype objects is a “living thing”! If a property in the prototype object is subsequently changed, it will affect all generated objects that inherit that property along the inheritance chain.

Prototypic inheritance by example

The working principle of inheriting objects by delegating properties will be best explained through examples.

Basic object:

We’ll start with a simple constructor function Initial() and its object prototype, and generate new objects using the new() keyword:

A constructor function and its prototype object in JavaScript are the closest we can get to the concept of a class from other programming languages. The newly generated object, during the very act of creating the object, copied all properties from the constructor function (Initial()), and linked its prototype object with the parent object. It should be emphasized that our new object (“initial1”), like any object generated with the “new” keyword, does not have its own constructor function (there is no “Initial1()” function), so its .constructor property points to the Initial() constructor function.

prototype diagram 1

Extended object:

The next part describes the procedure for creating a new object that extends the Start() function:

a) Creating a new constructor function and linking it to the Initial()

properties

Creating a modified “template object” starts with defining a new constructor function:

To inherit properties from another constructor function, we use the call() method. The call() method allows us to redefine the meaning of the “this” keyword. It’s up to usit is necessary that “this” written in the “Initial()” constructor function points to newly created objects created using the ModifiedInitial() function. Therefore, for the first parameter of the method (responsible for redefining “this”), we write “this”, because with this we define that the meaning of the word “this” indicates “this” (and that is by using the keyword new is actually a newly generated object). Through the other parameters of the call() method, we pass two parameters necessary for the First property and the Second property.

b) Connecting prototype objects with the chain of inheritance

Depending on how we want to assign methods to our “ModifiedInitial.prototype” object, associating the prototype object can be done with:

  1. new()

    Since every newly generated object is “hooked” into the inheritance chain, by assigning the newly generated object to our prototype object we give our prototype object access to the “inheritance chain”.

    However, this method is not recommended because by using it, we unnecessarily insert properties from the constructor function (propertyFirst, propertySecond) into the inheritance chain.

  2. Object.create()

    This approach is recommended, because in addition to creating a new object, the “Object.create()” method also directly connects two prototype objects (the prototype object of the newly created object and the parent prototype). By this binding to the parent prototype, our new object is “hooked” into the entire inheritance chain, so all methods from it are available to it.

    However, since our “ModifiedInitial()” constructor function was not used in this connection of prototype objects, the JavaScript engine does not recognize it, so it will search “deeper” along the inheritance chain, where it is found as the “Initial()” function. So our new object’s .constructor method currently points to the wrong “Initial()” object. This is not good, so we need to change it:

c) Adding new functionalities

After extending our new object with the properties and methods of the “parent” object, we can “enrich” our new object with new functionalities:

We can even change the extended method, by defining in our object a method with the same name that will “shadow” (eng. shadowing) the inherited method.

Thus, all objects created based on this modification have the following functionalities:

prototype diagram 2

Unified code from example: