JavaScript Classes & Inheritance class constructor extends super
This documentation covers everything you need to know about JavaScript classes, inheritance, constructors, the extends keyword, and the super keyword. It includes detailed explanations and practical examples to help you understand and apply these concepts effectively.
Introduction to Classes in JavaScript
What are Classes?
Imagine you have a blueprint for a house. This blueprint tells you how to construct a house with specific features like the number of windows, doors, and rooms. In object-oriented programming, a class serves a similar purpose. A class is a blueprint for creating objects that share similar properties and methods. Just as a blueprint can be used to build many houses, a class can be used to create many objects that follow the same structure.
In JavaScript, classes were introduced in ECMAScript 6 (ES6) to provide a more organized and systematic way of defining objects, emphasizing reusability and structure.
Classes are particularly useful for creating objects that have similar behavior or properties. For example, if you are building a game, you might have multiple enemies that share common behaviors like moving or attacking. A class can define the common behaviors of these enemies, and then you can create different enemy objects based on this class.
Why Use Classes?
Using classes in JavaScript offers several advantages:
- Organization: Classes help organize code into logical structures, making it easier to manage and understand.
- Reusability: Once a class is defined, it can be used to create multiple objects, promoting code reuse and reducing duplication.
- Prototypal Inheritance: Classes in JavaScript use prototypal inheritance, making it easier to share functionality between classes.
- Encapsulation: Classes can encapsulate data and methods, promoting better data hiding and security.
- Maintainability: Code that is well-structured and organized with classes is easier to maintain and update over time.
Defining a Class
The Class Declaration
To define a class in JavaScript, you can use the class
keyword followed by the class name. Here’s a simple example:
class Car {
constructor(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
displayDetails() {
return `This car is a ${this.year} ${this.make} ${this.model}.`;
}
}
In this example:
- We define a class named
Car
. - The
constructor
method is used to initialize new objects created from the class. - The
displayDetails
method is an instance method that returns a string describing the car.
You can create an instance of the Car
class using the new
keyword:
const myCar = new Car('Toyota', 'Corolla', 2020);
console.log(myCar.displayDetails()); // Output: This car is a 2020 Toyota Corolla.
The Class Expression
In addition to the class declaration, JavaScript also supports class expressions. Class expressions can be named or unnamed, and they are often used when defining a class inline. Here’s an example of an unnamed class expression:
const Animal = class {
constructor(species) {
this.species = species;
}
makeSound() {
console.log(`This ${this.species} makes a sound.`);
}
};
const lion = new Animal('lion');
lion.makeSound(); // Output: This lion makes a sound.
In this example:
- We define an unnamed class expression and assign it to the variable
Animal
. - We create an instance of the
Animal
class using thenew
keyword and call themakeSound
method to display a message.
Creating Instances of a Class
new
Keyword
Using the To create an instance of a class, you use the new
keyword followed by the class name and any required parameters. Here’s an example:
class Dog {
constructor(name, breed) {
this.name = name;
this.breed = breed;
}
bark() {
console.log(`${this.name} is barking.`);
}
}
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.bark(); // Output: Buddy is barking.
In this example:
- We define a
Dog
class with a constructor that initializes thename
andbreed
properties. - We create an instance of the
Dog
class using thenew
keyword and assign it to the variablemyDog
. - We call the
bark
method on themyDog
object.
Constructor Method
What is a Constructor?
The constructor is a special method for creating and initializing an object created with a class. A class can have only one constructor method. It is called automatically when a new instance of the class is created.
Syntax and Usage
The syntax for a constructor is straightforward:
class ClassName {
constructor(param1, param2) {
this.param1 = param1;
this.param2 = param2;
}
}
Here's an example with a Book
class:
class Book {
constructor(title, author, year) {
this.title = title;
this.author = author;
this.year = year;
}
getSummary() {
return `${this.title} by ${this.author}, published in ${this.year}.`;
}
}
const book1 = new Book('1984', 'George Orwell', 1949);
console.log(book1.getSummary()); // Output: 1984 by George Orwell, published in 1949.
In this example:
- The
Book
class has a constructor that initializes three properties:title
,author
, andyear
. - The
getSummary
method returns a string summarizing the book. - We create an instance of the
Book
class usingnew Book('1984', 'George Orwell', 1949)
and call thegetSummary
method on thebook1
object.
Class Methods
Defining Methods
Methods are functions that are defined inside a class. Methods are used to define the behavior of objects created from the class. Here’s a simple example:
class Vehicle {
constructor(type, color) {
this.type = type;
this.color = color;
}
startEngine() {
console.log(`The ${this.color} ${this.type} starts its engine.`);
}
stopEngine() {
console.log(`The ${this.color} ${this.type} stops its engine.`);
}
}
const car = new Vehicle('car', 'red');
car.startEngine(); // Output: The red car starts its engine.
car.stopEngine(); // Output: The red car stops its engine.
In this example:
- The
Vehicle
class has two methods:startEngine
andstopEngine
. - We create an instance of the
Vehicle
class and call thestartEngine
andstopEngine
methods.
Instance Methods
Instance methods are methods that act on instances of the class. They can access and modify the state of the object through the this
keyword. Here’s an example:
class Monitor {
constructor(brand, size) {
this.brand = brand;
this.size = size;
this.isPowerOn = false;
}
turnOn() {
this.isPowerOn = true;
console.log(`${this.brand} ${this.size} inch monitor turned on.`);
}
turnOff() {
this.isPowerOn = false;
console.log(`${this.brand} ${this.size} inch monitor turned off.`);
}
displayPowerStatus() {
if (this.isPowerOn) {
console.log('Monitor is on.');
} else {
console.log('Monitor is off.');
}
}
}
const myMonitor = new Monitor('Samsung', 27);
myMonitor.turnOn(); // Output: Samsung 27 inch monitor turned on.
myMonitor.displayPowerStatus(); // Output: Monitor is on.
myMonitor.turnOff(); // Output: Samsung 27 inch monitor turned off.
myMonitor.displayPowerStatus(); // Output: Monitor is off.
In this example:
- The
Monitor
class has three instance methods:turnOn
,turnOff
, anddisplayPowerStatus
. - We create an instance of the
Monitor
class and call its methods to control and check the power status of the monitor.
Static Methods
What are Static Methods?
Static methods are methods that belong to the class itself, rather than to instances of the class. They are called on the class directly, rather than on any specific object. Static methods are often used for utility functions that are related to the class but do not need access to instance-specific data.
Syntax and Usage
The syntax for a static method is as follows:
class ClassName {
static methodName() {
// method body
}
}
Here’s an example with a MathUtils
class that has a static method:
class MathUtils {
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
}
console.log(MathUtils.add(5, 3)); // Output: 8
console.log(MathUtils.multiply(5, 3)); // Output: 15
In this example:
- The
MathUtils
class has two static methods:add
andmultiply
. - We call these methods directly on the
MathUtils
class without creating an instance.
Encapsulation
Understanding Encapsulation
Encapsulation is one of the fundamental principles of object-oriented programming. It refers to bundling the data (properties) and methods that operate on the data into a single unit (a class) and restricting access to some of the object’s components. This helps in protecting the data inside the class from being accessed directly from outside the class, leading to better security and maintainability.
Implementing Private Fields and Methods
In JavaScript, private fields and methods were introduced in ECMAScript 2022. Private class fields and methods are only accessible within the class body and not from outside.
Private Fields
Private fields are denoted by the #
symbol and can only be accessed or modified from within the class. Here’s an example:
class BankAccount {
#balance;
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
console.log(`Deposited $${amount}. New balance: $${this.#balance}.`);
} else {
console.log('Invalid deposit amount.');
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
console.log(`Withdrew $${amount}. New balance: $${this.#balance}.`);
} else {
console.log('Invalid withdrawal amount.');
}
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(100);
account.deposit(200); // Output: Deposited $200. New balance: $300.
account.withdraw(150); // Output: Withdrew $150. New balance: $150.
console.log(account.getBalance()); // Output: 150
In this example:
- The
#balance
field is private and can only be accessed or modified from within theBankAccount
class. - The
deposit
andwithdraw
methods modify the private#balance
field. - The
getBalance
method returns the current balance.
Private Methods
Private methods, similar to private fields, are denoted by the #
symbol and can only be called from within the class. Here’s an example:
class Calculator {
#sum(a, b) {
return a + b;
}
#subtract(a, b) {
return a - b;
}
add(a, b) {
return this.#sum(a, b);
}
subtract(a, b) {
return this.#subtract(a, b);
}
}
const calculator = new Calculator();
console.log(calculator.add(5, 3)); // Output: 8
console.log(calculator.subtract(5, 3)); // Output: 2
In this example:
- The
#sum
and#subtract
methods are private and can only be called from within theCalculator
class. - The
add
andsubtract
methods are public and can be called from outside the class, but they internally call the private methods.
Inheritance in JavaScript
What is Inheritance?
Inheritance is a mechanism that allows a new class to inherit properties and methods from an existing class. This promotes code reuse and establishes a hierarchical relationship between classes. The new class, known as a derived or child class, inherits from the base or parent class.
extends
Keyword
The The extends
keyword is used to create a new class as a child of an existing class. Here’s a basic example:
class Animal {
constructor(species) {
this.species = species;
}
makeSound() {
console.log(`This ${this.species} makes a sound.`);
}
}
class Dog extends Animal {
constructor(species, name) {
super(species);
this.name = name;
}
makeSound() {
console.log(`${this.name} barks.`);
}
}
const myDog = new Dog('dog', 'Buddy');
myDog.makeSound(); // Output: Buddy barks.
In this example:
- The
Dog
class extends theAnimal
class. - The
Dog
class inherits themakeSound
method from theAnimal
class. - The
Dog
class overrides themakeSound
method to provide specific behavior for dogs.
Extending Built-in Objects
You can also extend built-in objects to add new features or modify existing behavior. Here’s an example of extending the Array
class:
class CustomArray extends Array {
sum() {
return this.reduce((acc, val) => acc + val, 0);
}
}
const myArray = new CustomArray(1, 2, 3, 4, 5);
console.log(myArray.sum()); // Output: 15
In this example:
- The
CustomArray
class extends the built-inArray
class. - The
CustomArray
class adds asum
method to calculate the sum of the array elements. - We create an instance of
CustomArray
and call thesum
method.
super
Keyword
The The super
keyword is used to call functions defined on a parent class. This includes calling the constructor of the parent class and calling methods on the parent class.
super
in Constructors
Using When you use the extends
keyword to create a subclass, you must call the parent class's constructor using super()
. Here’s an example:
class Pet {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Pet {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
console.log(`${this.name} barks.`);
}
}
const myPet = new Dog('Buddy', 'Golden Retriever');
myPet.speak(); // Output: Buddy barks.
console.log(myPet.name); // Output: Buddy
console.log(myPet.breed); // Output: Golden Retriever
In this example:
- The
Dog
class extends thePet
class. - The
Dog
class constructor callssuper(name)
to initialize thename
property defined in thePet
class. - The
Dog
class overrides thespeak
method to provide specific behavior for dogs.
super
for Calling Parent Methods
Using You can use super.methodName()
to call methods on the parent class. This is useful when you want to extend or modify the behavior of a method defined in the parent class.
class Vehicle {
constructor(type) {
this.type = type;
}
startEngine() {
console.log(`The ${this.type} vehicle starts its engine.`);
}
stopEngine() {
console.log(`The ${this.type} vehicle stops its engine.`);
}
}
class Car extends Vehicle {
constructor(type, model) {
super(type);
this.model = model;
}
startEngine() {
super.startEngine();
console.log(`The ${this.model} ${this.type} car starts its engine.`);
}
stopEngine() {
super.stopEngine();
console.log(`The ${this.model} ${this.type} car stops its engine.`);
}
}
const myCar = new Car('sedan', 'Toyota Camry');
myCar.startEngine(); // Output: The sedan vehicle starts its engine.
// The Toyota Camry sedan car starts its engine.
myCar.stopEngine(); // Output: The sedan vehicle stops its engine.
// The Toyota Camry sedan car stops its engine.
In this example:
- The
Car
class extends theVehicle
class. - The
Car
class overrides both thestartEngine
andstopEngine
methods but usessuper.methodName()
to call the parent class methods and extend their behavior.
Inheritance Chain
Understanding Prototype Chain
In JavaScript, every object has an internal property called __proto__
(or [[Prototype]]
). When a class extends another class, the child class's prototype is set to an instance of the parent class. This creates a prototype chain, allowing the child class to inherit methods and properties from the parent class.
instanceof
Operator
The The instanceof
operator is used to test whether an object is an instance of a specific class. Here’s an example:
class Animal {}
class Dog extends Animal {}
const myDog = new Dog();
console.log(myDog instanceof Dog); // Output: true
console.log(myDog instanceof Animal); // Output: true
console.log(myDog instanceof Object); // Output: true
In this example:
- We define an
Animal
class and aDog
class that extends theAnimal
class. - We create an instance of the
Dog
class. - The
instanceof
operator checks ifmyDog
is an instance ofDog
,Animal
, andObject
.
__proto__
Property
The The __proto__
property points to the parent class's prototype. Here’s an example:
class Vehicle {}
class Car extends Vehicle {}
const myCar = new Car();
console.log(myCar.__proto__ === Car.prototype); // Output: true
console.log(Car.prototype.__proto__ === Vehicle.prototype); // Output: true
console.log(Vehicle.prototype.__proto__ === Object.prototype); // Output: true
In this example:
- We define a
Vehicle
class and aCar
class that extends theVehicle
class. - We create an instance of the
Car
class. - We use the
__proto__
property to verify the prototype chain:myCar.__proto__
points toCar.prototype
,Car.prototype.__proto__
points toVehicle.prototype
, andVehicle.prototype.__proto__
points toObject.prototype
.
Overriding Methods
What is Method Overriding?
Method overriding occurs when a subclass provides a specific implementation of a method that is already defined in its parent class. This allows you to change or extend the behavior of the method in the subclass.
How to Override Methods
To override a method, simply define a method in the subclass with the same name as the method in the parent class. Here’s an example:
class Animal {
speak() {
console.log('This animal makes a sound.');
}
}
class Dog extends Animal {
speak() {
console.log('The dog barks.');
}
}
class Cat extends Animal {
speak() {
console.log('The cat meows.');
}
}
const myDog = new Dog();
const myCat = new Cat();
myDog.speak(); // Output: The dog barks.
myCat.speak(); // Output: The cat meows.
In this example:
- The
Dog
class and theCat
class both override thespeak
method defined in theAnimal
class. - When we call the
speak
method on instances ofDog
andCat
, the overridden methods are executed.
Accessing Parent Class Properties
super
to Access Parent Properties
Using To access properties from the parent class, you should use the super
keyword. Here’s an example:
class Vehicle {
constructor(type) {
this.type = type;
}
}
class Car extends Vehicle {
constructor(type, model) {
super(type);
this.model = model;
}
displayInfo() {
console.log(`This is a ${this.type} ${this.model}.`);
}
}
const myCar = new Car('sedan', 'Toyota Camry');
myCar.displayInfo(); // Output: This is a sedan Toyota Camry.
In this example:
- The
Car
class extends theVehicle
class. - The
Car
class constructor callssuper(type)
to initialize thetype
property of theVehicle
class. - The
displayInfo
method accesses thetype
property defined in theVehicle
class.
Accessing Parent Methods Directly
You can also use super.methodName()
to call methods defined in the parent class. Here’s an example:
class Vehicle {
startEngine() {
console.log('The vehicle starts its engine.');
}
}
class Car extends Vehicle {
startEngine() {
super.startEngine();
console.log('Car-specific start sequence.');
}
}
const myCar = new Car();
myCar.startEngine(); // Output: The vehicle starts its engine.
// Car-specific start sequence.
In this example:
- The
Car
class extends theVehicle
class. - The
Car
class overrides thestartEngine
method but also calls thestartEngine
method of theVehicle
class usingsuper.startEngine()
.
Abstract Classes (Simulating)
What are Abstract Classes?
An abstract class is a class that cannot be instantiated on its own and is designed to be subclassed. It may contain abstract methods that must be implemented by its subclasses. JavaScript does not have built-in support for abstract classes, but you can simulate them using constructors and throwing errors.
Simulating Abstract Classes in JavaScript
To simulate an abstract class, you can throw an error in the constructor if the class is instantiated directly. Here’s an example:
class Animal {
constructor() {
if (new.target === Animal) {
throw new TypeError('Cannot construct Animal instances directly.');
}
}
makeSound() {
throw new Error('Subclasses must implement makeSound method.');
}
}
class Dog extends Animal {
makeSound() {
console.log('The dog barks.');
}
}
// Trying to create an instance of the abstract class
try {
const animal = new Animal();
} catch (error) {
console.error(error.message); // Output: Cannot construct Animal instances directly.
}
// Creating an instance of the subclass
const myDog = new Dog();
myDog.makeSound(); // Output: The dog barks.
In this example:
- The
Animal
class is intended to be abstract and throws an error if its constructor is called directly. - The
Dog
class extends theAnimal
class and provides an implementation for themakeSound
method.
Summary of Key Concepts
Recapping Classes
- Class Declaration: Defines a new class using the
class
keyword. - Class Expression: Defines a class using a function-like expression.
- Constructor: A special method for creating and initializing objects.
- Methods: Functions defined inside a class to define object behavior.
- Static Methods: Methods that belong to the class, not to any object.
- Private Fields and Methods: Fields and methods that are only accessible within the class.
- Encapsulation: Bundling data and methods that operate on the data into a single unit and restricting access to some of the components.
Recapping Inheritance
- Inheritance: A mechanism to create new classes based on existing classes.
extends
Keyword: Used to define a subclass of another class.super
Keyword: Used to call functions defined on the parent class.- Prototype Chain: The chain of prototypes that allows method and property lookup.
instanceof
Operator: Used to test if an object is an instance of a specific class.__proto__
Property: Points to the parent class's prototype.
Exercises and Challenges
Practice Problems on Classes
-
Create a
Student
class with properties likename
,age
, andgrade
. Add methods to display the student's details and update the student's grade.class Student { constructor(name, age, grade) { this.name = name; this.age = age; this.grade = grade; } displayDetails() { return `Student Name: ${this.name}, Age: ${this.age}, Grade: ${this.grade}`; } updateGrade(newGrade) { this.grade = newGrade; console.log(`Grade updated to ${this.grade}.`); } } const student = new Student('Alice', 16, 'A'); console.log(student.displayDetails()); // Output: Student Name: Alice, Age: 16, Grade: A student.updateGrade('B'); // Output: Grade updated to B. console.log(student.displayDetails()); // Output: Student Name: Alice, Age: 16, Grade: B
-
Define a
Rectangle
class withlength
andwidth
properties. Add methods to calculate the area and perimeter.class Rectangle { constructor(length, width) { this.length = length; this.width = width; } getArea() { return this.length * this.width; } getPerimeter() { return 2 * (this.length + this.width); } } const rectangle = new Rectangle(5, 3); console.log(`Area: ${rectangle.getArea()}`); // Output: Area: 15 console.log(`Perimeter: ${rectangle.getPerimeter()}`); // Output: Perimeter: 16
Practice Problems on Inheritance
-
Create a
Vehicle
class and aCar
subclass. TheVehicle
class should have properties and methods for starting and stopping the engine. TheCar
class should override these methods to include car-specific behavior.class Vehicle { startEngine() { console.log('The vehicle starts its engine.'); } stopEngine() { console.log('The vehicle stops its engine.'); } } class Car extends Vehicle { startEngine() { super.startEngine(); console.log('Car-specific start sequence.'); } stopEngine() { super.stopEngine(); console.log('Car-specific stop sequence.'); } } const myCar = new Car(); myCar.startEngine(); // Output: The vehicle starts its engine. // Car-specific start sequence. myCar.stopEngine(); // Output: The vehicle stops its engine. // Car-specific stop sequence.
-
Define a
Shape
class and aCircle
subclass. TheShape
class should have a method for calculating the area. TheCircle
class should override the method to calculate the area of a circle.class Shape { calculateArea() { throw new Error('This method must be implemented by subclasses.'); } } class Circle extends Shape { constructor(radius) { super(); this.radius = radius; } calculateArea() { return Math.PI * this.radius ** 2; } } const circle = new Circle(5); console.log(`Area: ${circle.calculateArea()}`); // Output: Area: 78.53981633974483
These exercises and challenges help you practice defining classes, creating instances, and using inheritance in JavaScript. By working through these exercises, you gain a deeper understanding of how to use classes and inheritance effectively in your JavaScript programs.