Prototype-Based Inheritance
JavaScript objects are connected together rather than having their properties copied from a “class” to a “instance”. In the standard, each JavaScript object has a unique internal attribute called [[Prototype]] that refers to either null or another object. The object’s prototype is the name given to this referenced item.
JavaScript initially determines whether an object has a property or method as an own property before attempting to access it. JavaScript automatically follows the [[Prototype]] link and searches the object’s prototype for the property if it is not present on the object itself. Until the property is discovered or an object with a null prototype is reached, this process keeps going up the chain of prototypes. The prototype chain is the name given to this collection of connected items. The access usually yields undefined if the property is not present anywhere in the chain. Higher up the prototype chain, methods are often inherited attributes.
Object.prototype is inherited by objects made with object literals {} or new Object(). The foundational built-in JavaScript class Object.prototype is located at the top of the inheritance tree for the majority of objects.
Creating Objects that Inherit with Object.create()
The Object.create() method is one straightforward approach to generate a new object that inherits from a certain prototype object. You can specifically designate which object should be used as the prototype for the newly formed object using this function.
Here’s an example using Object.create():
// Define an object to serve as a prototype
let animal = {
eats: true,
walk() {
console.log(“Animal walks”);
}
};
// Create a new object (rabbit) that inherits from animal
let rabbit = Object.create(animal);
// rabbit now has access to properties and methods from animal
console.log(rabbit.eats); // Output: true (inherited from animal)
rabbit.walk(); // Output: Animal walks (inherited from animal)
// rabbit can also have its own properties
rabbit.jumps = true;
console.log(rabbit.jumps); // Output: true (own property)
The eats and walk characteristics are not explicitly stated on the rabbit in this case. JavaScript searches the prototype chain, locates these attributes on the animal object (rabbit’s prototype), and applies them when rabbit.eats or rabbit.walk() is called. The objects that inherit from the prototype object in this case, an animal instantly reflect any changes made to it (rabbit). For directly constructing these links, Object.create() is regarded as a hero.
Subclassing
The idea of subclassing is when a new class (the subclass) takes on traits and behaviors from an already-existing class (the superclass). This is accomplished in JavaScript by setting up the subclass prototype to inherit from the superclass prototype. This guarantees that across the prototype chain, instances of the subclass inherit from both the prototype of the subclass and the prototype of the superclass.
Prior to ES6, subclassing was accomplished manually by using Object.create() to configure the prototype of the subclass’s constructor function to inherit from the prototype of the superclass:
// Superclass (old way using function constructor)
function Animal(name) {
this.name = name; // Own property on instance
}
Animal.prototype.run = function() { // Method on prototype
console.log(`${this.name} runs.`);
}; //
// Subclass (old way)
function Rabbit(name) {
Animal.call(this, name); // Call superclass constructor to set own properties
// Add own properties specific to Rabbit
this.isFluffy = true;
}
// Set Rabbit’s prototype to inherit from Animal’s prototype
Rabbit.prototype = Object.create(Animal.prototype); //
// Correct the constructor reference on the prototype
Rabbit.prototype.constructor = Rabbit; //
// Add methods specific to Rabbit’s prototype
Rabbit.prototype.hop = function() {
console.log(`${this.name} hops.`);
};
const whiteRabbit = new Rabbit(“White Rabbit”);
whiteRabbit.run(); // Output: White Rabbit runs. (Inherited)
whiteRabbit.hop(); // Output: White Rabbit hops. (Own method)
With the introduction of the class keyword in ES6, subclassing became syntactically simpler using the extends keyword. Under the hood, extends still utilizes the prototype mechanism.
// Superclass (ES6 class syntax)
class Animal {
constructor(name) {
this.name = name;
}
run() {
console.log(`${this.name} runs.`);
}
} //
// Subclass (ES6 class syntax)
class Rabbit extends Animal { // Use ‘extends’ keyword
constructor(name) {
super(name); // Call superclass constructor using ‘super()’
this.isFluffy = true;
}
hop() {
console.log(`${this.name} hops.`);
}
// Can override superclass methods
run() {
super.run(); // Can call the superclass method using ‘super’
console.log(`${this.name} runs faster.`);
}
} //
const marchHare = new Rabbit(“March Hare”);
marchHare.run(); // Output: March Hare runs. \n March Hare runs faster.
marchHare.hop(); // Output: March Hare hops.
The prototype chain is immediately built up correctly using the extends keyword. A subclass that has its own constructor must first invoke the constructor of the superclass by calling super(). The class prototype is expanded to include methods that are explicitly defined in the class body. Code becomes more reusable and maintainable as a result of attributes and methods being passed down through the prototype chain.