Mastering Object-Oriented Programming (OOP) in JavaScript

bernard bebenibernard bebeni
7 min read

Object-Oriented Programming (OOP) is an essential concept in JavaScript. It enables developers to write modular, reusable, and maintainable code by organizing it around data (objects), rather than functions and logic. This article explores key OOP concepts in JavaScript covering classes, objects, inheritance, encapsulation, and polymorphism. Additionally, it offers practical examples for each concept.

Prerequisites

  1. Basic JavaScript syntax and concepts, such as variables, data types, control structures, and basic operators.

  2. Understanding of functions and scope.

  3. Understanding of prototypes.

  4. Knowledge of ES6+ features essential for writing clean and efficient code.

Key Concepts of OOP in JavaScript

  1. Classes and Objects

  2. Encapsulation

  3. Inheritance

  4. Polymorphism

  1. Classes and Objects

In JavaScript, classes are blueprints for creating objects. Objects are instances of classes that can have properties and methods.

Creating a Class

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    greet() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
}

const person1 = new Person("John", 30);
person1.greet(); // Output: Hello, my name is John and I am 30 years old.

Explanation:

  • class Person {...} defines a new class named Person.

  • constructor(name, age) {...} is a special method for creating and initializing an object created with the Person class.

  • this.name = name; assigns the name parameter to the name property of the instance.

  • this.age = age; assigns the age parameter to the age property of the instance.

  • greet() {...} is a method within the Person class. It logs a greeting message to the console using template literals to embed the name and age properties of the instance.

  • const person1 = new Person("John", 30); creates a new instance of the Person class with the name "John" and age 30.

  • person1.greet(); calls the greet() method on the person1 instance.

  1. Encapsulation

Encapsulation bundles data (properties) and methods (functions) into a class. It protects object states and controls data access. This approach also makes code more modular and flexible.

Using Encapsulation

class Person {
    constructor(name, age) {
        this._name = name;
        this._age = age;
    }

    get name() {
        return this._name;
    }

    set name(newName) {
        if (newName.length > 0) {
            this._name = newName;
        } else {
            console.log("Name cannot be empty");
        }
    }

    greet() {
        console.log(`Hello, my name is ${this._name} and I am ${this._age} years old.`)
    }
}

const person2 = new Person("Jane", 20);
console.log(person2.name); // Output: Jane
person2.name = "Doe";
console.log(person2.name); // Output: Doe

Explanation

  • The properties _name and _age are encapsulated within the class.

  • The use of getter and setter methods (get name() and set name(newName)) allows controlled access to these properties.

  • The underscore (_) prefix indicates that these properties are intended to be private.

  • This helps in protecting the internal state of the object and ensures that any changes to the properties are validated.

  • The get name() method allows reading the _name property.

  • return this._name; returns the value of the _name property.

  • The set name(newName) method allows modifying the _name property with validation.

  • console.log(person2.name); accesses the name property using the getter method.

  • person2.name = "Doe"; uses the setter method to update the _name property to "Doe".

  1. Inheritance

Inheritance creates a new class from an existing one. The new class, called a subclass, inherits properties and methods. The existing class is the superclass or base class. It's key for building code that's modular, reusable, and easy to maintain. Additionally, it allows you to expand on existing code, create hierarchies, and write flexible, abstract code.

Implementing Inheritance

class Vehicle {
    constructor(make, model) {
        this.make = make;
        this.model = model;
    }

    start() {
        console.log(`${this.make} ${this.model} is starting.`);
    }

    stop() {
        console.log(`${this.make} ${this.model} is stopping`)
    }
}

class Car extends Vehicle {
    constructor(make, model, doors) {
        super(make, model); // Call the constructor of the base class
        this.doors = doors;
    }

    honk() {
        console.log(`${this.make} ${this.model} is honking`)
    }
}

class Bike extends Vehicle {
    constructor(make, model, type) {
        super(make, model);
        this.type = type;
    }

    ringBell() {
        console.log(`${this.make} ${this.model} is ringing the bell`)
    }
}

const myCar = new Car("Toyota", "Corolla", 4);
myCar.start(); // Output: Toyota Corolla is starting
myCar.honk();  // Output: Toyota Corolla is honking.
myCar.stop();  // Output: Toyota Corolla is stopping.

const myBike = new Bike("Giant", "Escape", "Road");
myBike.start(); // Output: Giant Escape is starting.
myBike.ringBell(); // Output: Giant Escape is ringing the bell.
myBike.stop(); // Output: Giant Escape is stopping.

Explanation:

  • class Vehicle { ... } creates a new class named Vehicle.

  • this.make = make; assigns the make parameter to the make property of the instance.

  • this.model = model; assigns the model parameter to the model property of the instance.

  • start() { ... } is a method that logs a message indicating the vehicle is starting.

  • stop() { ... } is a method that logs a message indicating the vehicle is stopping.

  • class Car extends Vehicle { ... } defines a new class named Car that extends the Vehicle class.

  • constructor(make, model, doors) { ... } is a special method for creating and initializing an object created with the Car class.

  • super(make, model); calls the constructor of the base classVehicle to initialize the make and model properties.

  • this.doors = doors; assigns the doors parameter to the doors property of the instance.

  • honk() { ... } is a method that logs a message indicating the car is honking.

  • class Bike extends Vehicle { ... } defines a new class Bike that extends the Vehicle class.

  • constructor(make, model, type) { ... } is a special method. It creates and sets up an object made with the Bike class.

  • super(make, model); calls the constructor of the base class Vehicle to initialize the make and model properties.

  • this.type = type; assigns the type parameter to the type property of the instance.

  • ringBell() { ... } is a method that logs a message indicating the bike is ringing the bell.

  • const myCar = new Car("Toyota", "Corolla", 4); creates a new instance of the Car class with the make "Toyota", model "Corolla", and doors 4.

  • const myBike = new Bike("Giant", "Escape", "Road"); creates a new instance of the Bike class with the make "Giant", model "Escape", and type "Road".

  • myCar.start(); calls the start method on the myCar instance, logging "Toyota Corolla is starting."

  • myCar.honk(); calls the honk method on the myCar instance, logging "Toyota Corolla is honking."

  • myBike.stop(); calls the stop method on the myBike instance, logging "Giant Escape is stopping."

  • myBike.ringBell(); calls the ringBell method on the myBike instance, logging "Giant Escape is ringing the bell."

  1. Polymorphism

Polymorphism is a concept that allows using a function in various ways. It means a single function can have different forms and behaviors. In JavaScript, developers achieve this by overriding methods and implementing interfaces.

i. Method Overriding

Method overriding allows a subclass to provide a specific implementation of a method that is already defined in its superclass.

Example: Method Overriding

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.");
    }
}

// Creating instances of Dog and Cat
const dog = new Dog();
const cat = new Cat();

// Calling the speak method on each instance
dog.speak(); // Output: The dog barks.
cat.speak(); // Output: The cat meows.

Explanation:

  • The Animal class has a method speak.

  • The Dog and Cat classes override the speak method to provide specific implementations.

  • When speak is called on instances of Dog and Cat, execute the overridden methods in their respective subclasses.

ii. Interface Implementation

Unlike some OOP languages, JavaScript doesn't have interfaces. However, you can mimic this. Define a common method structure that different classes can use.

Example: Interface-like Behavior

class Bird {
    fly() {
        console.log("This bird is flying.");
    }
}

class Sparrow extends Bird {
    fly() {
        console.log("The sparrow flies swiftly.");
    }
}

class Eagle extends Bird {
    fly() {
        console.log("The eagle soars high.");
    }
}

// Array of different birds
const birds = [new Sparrow(), new Eagle()];

// Calling the fly method on each bird
birds.forEach(bird => {
    bird.fly();
});
// Output:
// The sparrow flies swiftly.
// The eagle soars high.

Explanation:

  • The Bird class has a method fly.

  • The Sparrow and Eagle classes implement their own versions of the fly method.

  • By treating all objects as instances of Bird, we can call fly on each and get the correct implementation.

Conclusion

Mastering object-oriented programming in JavaScript enhances code organization and robustness. By leveraging classes, encapsulation, inheritance, and polymorphism, developers can create more scalable and maintainable applications. Dive deeper into these concepts and elevate your coding skills to the next level. Explore the resources below and start building more powerful JavaScript applications today!

For more in-depth information and examples, consider exploring the following resources:

MDN Web Docs: Object-oriented programming

Geeks for Geeks: Object Oriented Programming in JavaScript

1
Subscribe to my newsletter

Read articles from bernard bebeni directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

bernard bebeni
bernard bebeni