JavaScript supports inheritance as part of its object-oriented programming (OOP) features. Although JavaScript is prototype-based rather than class-based, it still allows inheritance, which can be achieved through prototype chaining or, more recently, through ES6 classes. Here’s how inheritance works in JavaScript using classes:
Inheritance Using ES6 Classes
Let’s say we have a base class called Animal with a method speak. Then, we create a derived class called Dog that inherits from Animal and extends it with a new method, bark.
// Base class
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
// Derived class
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call the parent class constructor with name
this.breed = breed;
}
bark() {
console.log(`${this.name} barks!`);
}
}
// Creating instances
const animal = new Animal("Generic Animal");
animal.speak(); // Output: "Generic Animal makes a sound."
const dog = new Dog("Rex", "Golden Retriever");
dog.speak(); // Output: "Rex makes a sound."
dog.bark(); // Output: "Rex barks!"
Explanation
- Base Class (Animal): This class has a constructor that initializes the name property and a method speak() that logs a message.
- Derived Class (Dog): This class extends Animal, which means it inherits the properties and methods of Animal. In Dog's constructor, we use super(name) to call the parent class's constructor to initialize name. Additionally, Dog has a new method bark().
Key Points:
- extends allows Dog to inherit from Animal.
- super calls the constructor of the parent class.
- Dog inherits speak() from Animal and has its own method bark().
- This is how JavaScript implements inheritance, giving it OOP-like behavior similar to traditional class-based languages.
Inheritance Using Prototype & Constructor Function
Before the introduction of ES6 classes, JavaScript handled inheritance through constructor functions and prototypes. Here’s how you can achieve inheritance using these older techniques:
Example: Inheritance Using Constructor Function and Prototype
Let’s use the same example of an Animal "class" and a Dog "class" by defining them with constructor functions and setting up inheritance through prototypes.
// Base "class" constructor function
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound.`);
};
// Derived "class" constructor function
function Dog(name, breed) {
Animal.call(this, name); // Call the Animal constructor with `this` context
this.breed = breed;
}
// Inherit the prototype of Animal
Dog.prototype = Object.create(Animal.prototype);
// Set the constructor property back to Dog (optional, but good practice)
Dog.prototype.constructor = Dog;
// Add a new method specific to Dog
Dog.prototype.bark = function() {
console.log(`${this.name} barks!`);
};
// Creating instances
const animal = new Animal("Generic Animal");
animal.speak(); // Output: "Generic Animal makes a sound."
const dog = new Dog("Rex", "Golden Retriever");
dog.speak(); // Output: "Rex makes a sound."
dog.bark(); // Output: "Rex barks!"
Explanation
Base "class" (Animal):
- We create a constructor function Animal that initializes the name property.
- We define a method speak() on Animal.prototype. This allows all instances of Animal to inherit this method.
Derived "class" (Dog):
- The Dog constructor function initializes both name and breed.
- We call Animal.call(this, name) inside Dog to initialize name in the context of the Dog instance. This is similar to calling super(name) in ES6 classes.
- To set up inheritance, we assign Dog.prototype to Object.create(Animal.prototype). This makes Dog instances inherit methods from Animal.prototype.
- We then set Dog.prototype.constructor = Dog as a best practice so that instances know they were constructed by Dog.
Adding the bark Method:
- We add a new bark() method directly to Dog.prototype, so only Dog instances have access to bark, while still inheriting speak from Animal.
Key Points:
- Using Animal.call(this, name) allows Dog to reuse Animal's constructor code.
- Object.create(Animal.prototype) sets up prototype inheritance.
- Adding methods to Dog.prototype extends functionality specific to Dog.
- This approach simulates class-based inheritance in JavaScript without using ES6 classes, leveraging constructor functions and prototypes for inheritance.
Constructor function call Explained
- Animal.call(this, name);
The call function in JavaScript is a method available on all functions, which allows you to call (or invoke) a function with a specific this context. In the line: Animal.call(this, name);
we are using call to invoke the Animal constructor function, but with the this context set to the current instance of Dog. Here’s a breakdown of why this is useful:
Purpose of Animal.call(this, name)
In JavaScript, when we define a constructor function, the properties defined with this belong to the instance created by that constructor. Since Dog is meant to "inherit" properties from Animal, we use call to apply Animal's constructor to the Dog instance.
How call Works in This Context
- Syntax: The syntax for call is functionName.call(thisArg, arg1, arg2, ...).
- thisArg: Specifies what this should refer to inside the function. Here, we pass this, which refers to the new instance of Dog being created.
- arg1, arg2, ...: Any arguments that the function expects. In this case, name is passed as an argument to Animal.
Animal.call(this, name) Explanation:
When Animal.call(this, name) runs, it calls the Animal constructor and sets this inside Animal to the current Dog instance. As a result, this.name = name inside Animal will set the name property on the Dog instance, allowing Dog to inherit and initialize properties from Animal.
Example Breakdown with and without call:
Consider the difference if we didn’t use call:
function Animal(name) {
this.name = name;
}
function Dog(name, breed) {
// Without `call`, this.name won't be set for Dog
this.breed = breed;
}
const dog = new Dog("Rex", "Golden Retriever");
console.log(dog.name); // undefined
console.log(dog.breed); // "Golden Retriever"
Here, name is not initialized in Dog, so dog.name is undefined.
By using Animal.call(this, name), name is properly initialized:
function Dog(name, breed) {
Animal.call(this, name); // Call Animal constructor on Dog instance
this.breed = breed;
}
const dog = new Dog("Rex", "Golden Retriever");
console.log(dog.name); // "Rex"
console.log(dog.breed); // "Golden Retriever"
Summary
- Animal.call(this, name) calls the Animal constructor with this set to the new Dog instance.
- This allows Dog to inherit and initialize properties defined in Animal.
- call is essential here for setting up inheritance of instance properties when using constructor functions.
No comments:
Post a Comment