Community

Blog
×
Community Blog Prototypes and Inheritance in JavaScript

Prototypes and Inheritance in JavaScript

In this article, we will discuss JavaScript object prototypes, their inheritance and chains, as well as learn how constructors are used to extend objects with new prototypes.

By Alex Mungai Muchiri, Alibaba Cloud Tech Share Author. Tech Share is Alibaba Cloud's incentive program to encourage the sharing of technical knowledge and best practices within the cloud community.

JavaScript is a language that uses prototypes very widely to share methods and properties of objects. As a consequence, it is very easy to clone and extend generalized objects, a method that is referred to as prototypical inheritance. Notably, it is a very different technique from the class inheritance and should not be confused. Most object-oriented programming languages such as PHP, Python, and Java are class-based, a stark difference from the prototypical inheritance of JavaScript. Those languages usually have object blueprints for classes. This article is dedicated for the understanding of JavaScript object prototypes, their inheritance and chains. You will also learn how constructors are used to extend objects with new prototypes.

Prototypes in JavaScript

It is assumed that you already are acquainted with object data types in JS, creating objects and how to modify object properties. Prototypes allow us to extend objects based on the fact that each JavaScript object includes [[Prototype]] as an internal property. To see this in action, let us create a new empty project like so:

let x = {};

this is a practical method that is used to create objects, but there is also another approach that uses the object constructor like so:

let x = new Object().

The property [[Prototype]] has double square brackets, which is an indicator of its internal nature, that is, it is not accessible directly in code. Accordingly, we need to use the getPrototypeOf() method to obtain[[Prototype]] of the object.

Object.getPrototypeOf(x);

The method yields the following built-in properties and methods

Output
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

It is also plausible to use proto property to examine the[[Prototype]]. The proto method also yields an object's internal properties.

Please do note that proto should not be used in production code since it is a legacy feature that is not universally compatible with all available browsers. Nonetheless, for purposes of this tutorial, we can still use it for demonstration but keep the previous sentiment in mind.

x.__proto__;

we would still get the same output as when we make use of the getPrototypeOf().

Output
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

Ideally, every object in JavaScript should have a [[Prototype]] so that it allows for the linking of two or ore objects.

All JavaScript objects created or built-in objects consist of a [[Prototype]] property. Using the internal property, we can then refer to objects between each other, as has been explored further down, using prototype property.

Inheritance

JavaScript initially searches on an object whenever you attempt to access its property or method, and if not found, it then proceeds to in its [[Prototype]]. If there still are no matches for the property at the [[Prototype]], JavaScript then tries checking from the linked object's prototype until all objects in the chain are all covered n the search. Each chain contains Object.prototype at the end, whereby, there is an inheritance of methods and properties from Object. Searching beyond the chain always returns a null output. In our case above we have an empty object x, that bears inheritance from Object. If Object has any associated properties and methods, such as toString(), they all could be used x. for instance, we could have:

x.toString();
Output
[object Object]

This is a prototype chain with only one link that could be defined as x -> Object. We can verify if there are any other links by chaining [[Prototype]] properties twice. If there are no other chains, the output is null.

x.__proto__.__proto__;
Output
null

we are now going to explore a different type of object. Specifically, if you have already encountered JavaScript Arrays, you must be familiar with pop() and push(), which are other built-in methods. These two methods are always accessible when new arrays are created. The reason that is so is due to the inherent accessibility of the Array.prototype by all newly created arrays. Use the line below to create a new array:

let y = [];

there is also the method of using array constructors to create new arrays that is accomplished like so:

let y = new Array()

Let us now examine the new array's [[Prototype]] like so, and it will be evident that it contains far more properties and methods compared to the x object since it inherits all properties associated with Array.prototype.

y.__proto__;
[constructor: ƒ, concat: ƒ, pop: ƒ, push: ƒ, …]

There is a prototype's constructor property on our array, which is vividly set to Array(). What it does is to return an object's constructor function, a mechanism that allows the use of functions to construct objects.

It is further possible to chain two prototypes in our longer chain into such syntax as: y -> Array -> Object.

When the Array.prototype is examined, we get the following

y.__proto__.__proto__;
Output
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

It is observed that Object.prototype is referred to in the chain. We could perform a further test on the internal [[Prototype]] and compared it with the constructor function's prototype property to determine if they yield the same result:

y.__proto__ === Array.prototype;            // true
y.__proto__.__proto__ === Object.prototype; // true

The isPrototypeOf() method could also be applied here

Array.prototype.isPrototypeOf(y);      // true
Object.prototype.isPrototypeOf(Array); // true

An operator's instanceof is a method that could be used to test the appearance of a constructor's prototype property within the prototype chain as indicated below:

y instanceof Array; // true

In summary, we have determined that there is a hidden internal [[Prototype]] property in all JavaScript objects. Such properties are accessible using the proto method. We can extend objects due to the underlying fact that the could be extended through inheritance of their constructor's [[Prototype]] properties and methods. When chained, all objects added to the chain inherit the properties and methods to the end of the chain, which ends with Object.prototype.

Constructor Functions

Essentially, constructor functions create new JavaScript objects. Launching new instances that base on constructor functions, we use new operator. However, JavaScript also contains built-in constructors like new Array() and new Date(), but is also possible to create new ones that we the use create objects.

For instance, we could be trying to create a simple role-play game that is based on text. In our game, a player shall choose a character and the associated class such as doctor, soldier, robber, and so on.

All our game characters have several aspects in common such as name, level and points, which means that it is preferable to have a common constructor for characters. At the same time, each character shall be limited to accessing only its own abilities, which vary between the characters. We are going to use prototype inheritance and constructor functions to attain out objective.

As a start, do not that constructor functions are like any other regular functions. The key differentiator between the two is that constructors are called on by instances with new key words. It is a JavaScript requirement that we capitalize a constructor function's first letter. See example:

// Initialize a constructor function for a new Actor
function Actor(name, level) {
  this.name = name;
  this.level = level;
}

The example demonstrates the creation of a constructor function (Actor) consisting of the parameters name and level. All the characters of our text game have a name and an associated level, it would be ideal that they all share the properties contained in our constructor function above. The keyword this indicates new instances that have been created. Thus, for all objects to have a have a name property, this.name should be set too the name parameter as has been shown in the example.

Let us try to build a new instance using new.

let actor1 = new Actor('Bjorn', 1);

Now, let us try to console actor1, and observe if we have created a new object and what its properties would be:

Output
Actor {name: "Bjorn", level: 1}

Further, we examine the [[Prototype]] of actor1, which should output the constructor as Actor().

Object.getPrototypeOf(actor1);
Output
constructor: ƒ Actor(name, level)

Do keep in mind that the actor1.__proto__ method would also yield the same result but like we mentioned, it has limited browser compatibility and therefore not very appropriate.

Our constructor only has properties defines as we are yet to define the methods. In JavaScript, we should define a prototype's methods so that efficiency and code readability are improved.

Accordingly, let us now define a method for Actor using prototype. We are going to use the greet() method for demonstration.

characterSelect.js

// Add greet method to the Actor prototype
Actor.prototype.greet = function () {
  return `${this.name} says hello.`;
}

Actor now has greet()method as part of the prototype. Accordingly, since hero1 constitutes an instance of Actor, actor1 should inherit the method as well:

Actor1.greet();
Output    
"Bjorn says hello."

If you need to, you can inspect Actor's [[Prototype]] and you will find that greet() is now available. However, we still need to create character classes for our Actors. As we noted earlier, our game Actors will possess different abilities. It would thus be illogical if all the characters had their abilities contained in the Actor constructor, since they would all inherit the same abilities yet they should be different. How we approach the problem is that we create a new constructor function that is still connected to the original Actor.

We then proceed to export one constructor's properties to another by use of the call() method. Let us create constructors for a soldier and a doctor:

// Initialize Soldier constructor
function Soldier(name, level, weapon) {
  // Chain constructor with call
  Actor.call(this, name, level);

  // Add a new property
  this.weapon = weapon;
}

// Initialize Doctor constructor
function Doctor(name, level, spell) {
  Doctor.call(this, name, level);

  this.spell = spell;
}

As you can observe, we have created constructors that contain an Actor's properties as well as a few additional ones. Now, we shall add new methods for our Soldier and Doctor. The attack() method applies to Soldier while the heal() method applies to Doctor.

Soldier.prototype.attack = function () {
  return `${this.name} attacks with the ${this.weapon}.`;
}

Doctor.prototype.heal = function () {
  return `${this.name} casts ${this.spell}.`;
}

Let us proceed to include the two classes in our characters

characterSelect.js
const actor1 = new Soldier('Bjorn', 1, 'axe');
const actor2 = new Doctor('Kanin', 1, 'cure');

What is does in effect is as follows:

Recognise actor1 as a soldier and the added properties:

Soldier {name: "Bjorn", level: 1, weapon: "axe"}

Let us try using the methods that we have introduced on the Soldier prototype.

actor1.attack();
Console
"Bjorn attacks with the axe."

However, if we try to use the second method we get the result below:

actor1.greet();
Output
Uncaught TypeError: actor1.greet is not a function

Evidently, the new methods are not automatically inherited by the call(). What we need is to link the prototypes is to use the Object.create(). However, it needs to be located before the methods we have created for our prototypes.

Soldier.prototype = Object.create(Actor.prototype);
Doctor.prototype = Object.create(Actor.prototype);

// We add all other prototype methods below

The Actor's prototype methods have now been added to instances of Soldier or Doctor.

Doctor.
actor1.greet();
Output
"Bjorn says hello."

Below is the entire code for the character creation

// Initialize constructor functions
function Actor(name, level) {
  this.name = name;
  this.level = level;
}

function Soldier(name, level, weapon) {
  Actor.call(this, name, level);

  this.weapon = weapon;
}

function Doctor(name, level, spell) {
  Actor.call(this, name, level);

  this.spell = spell;
}

// Link prototypes and add prototype methods
Soldier.prototype = Object.create(Actor.prototype);
Doctor.prototype = Object.create(Actor.prototype);

Actor.prototype.greet = function () {
  return `${this.name} says hello.`;
}

Soldier.prototype.attack = function () {
  return `${this.name} attacks with the ${this.weapon}.`;
}

Doctor.prototype.heal = function () {
  return `${this.name} casts ${this.spell}.`;
}

// Initialize individual character instances
const actor1 = new Soldier('Bjorn', 1, 'axe');
const actor2 = new Doctor('Kanin', 1, 'cure');

What we have achieved in this tutorial is demonstrate the concept of prototypes and inheritance. We successfully created an Actor class that bears the base properties. We then created the Soldier and Doctor character classes inheriting from our initial constructor. Finally, we added to the prototypes new methods and built instances for each character.

Conclusion

Unlike traditional class-based programming languages, JavaScript is based on prototypes. We have explored its prototypes and learnt how to link objects into chains through [[Prototype]] property. We've also passed down properties of prototypes through the chain.

Don't have an Alibaba Cloud account? Sign up for an account and try over 40 products for free worth up to $1200. Get Started with Alibaba Cloud to learn more.

0 0 0
Share on

Alex

17 posts | 2 followers

You may also like

Comments