Back to blog

Sunday, March 2, 2025

JavaScript Object-Oriented Programming (OOP) Explained

cover

JavaScript is a versatile programming language that supports multiple paradigms, including Object-Oriented Programming (OOP). OOP is a programming model centered around the concept of objects, which can contain data and code to manipulate that data. In JavaScript, OOP allows you to create reusable code and maintain manageable codebases. In this blog, we will explore the core principles of JavaScript OOP, including classes, inheritance, encapsulation, and polymorphism.

What is Object-Oriented Programming?

Object-Oriented Programming is a methodology in programming that uses objects and their interactions to design applications and computer programs. The primary features of OOP include:

  • Encapsulation: The bundling of data (properties) and methods (functions) that operate on the data into a single unit or class. Encapsulation restricts direct access to some of an object's components, which can prevent the accidental modification of data.
  • Inheritance: The mechanism where a class (child class) inherits properties and methods from another class (parent class). This helps in reusability and organizing code.
  • Polymorphism: The ability of objects to be treated as instances of their parent class, allowing one interface to represent different underlying forms (data types).
  • Abstraction: The process of hiding complex implementation details and showing only the functionality to the users. In JavaScript, abstraction can be achieved using classes and modules.

Setting Up Your Environment

Before diving into OOP, ensure you have a code editor like Visual Studio Code or Sublime Text, and a modern web browser like Google Chrome or Mozilla Firefox for testing your code.

Creating Your First Class in JavaScript

Let's start by creating a simple class in JavaScript. In Earlier versions of JavaScript, OOP was handled using constructor functions and prototype methods. However, ES6 introduced classes, which made OOP more intuitive and easier to understand.

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

  getDetails() {
    return `${this.year} ${this.make} ${this.model}`;
  }
}

const myCar = new Car('Toyota', 'Corolla', 2020);
console.log(myCar.getDetails()); // Output: 2020 Toyota Corolla
Explanation
  • Class Declaration: The class keyword is used to declare a new class named Car.
  • Constructor: The constructor function is used to create and initialize an object created with a class. It is called during the creation of an instance of the class.
  • Methods: Methods like getDetails can be added to a class to provide specific functionality to objects.
  • Creating an Instance: The new keyword is used to create an instance of a class.

Inheritance in JavaScript

Inheritance allows a class to inherit properties and methods from another class. This helps in promoting code reuse and maintaining a clean architecture.

Syntax

To create a subclass, you use the extends keyword.

class ElectricCar extends Car {
  constructor(make, model, year, batteryCapacity) {
    super(make, model, year); // Call the parent class constructor
    this.batteryCapacity = batteryCapacity;
  }

  getDetails() {
    return `${super.getDetails()} with ${this.batteryCapacity}kWh battery`;
  }
}

const myElectricCar = new ElectricCar('Tesla', 'Model S', 2022, 75);
console.log(myElectricCar.getDetails()); // Output: 2022 Tesla Model S with 75kWh battery
Explanation
  • Extends Keyword: The extends keyword is used to create a subclass. In this example, ElectricCar extends Car.
  • super(): The super() function is called to call the constructor of the parent class (Car). This must be done before using this in the subclass.
  • Method Overriding: The getDetails method in the ElectricCar class overrides the method from the Car class, allowing for custom behavior while still leveraging the functionality of the parent class.

Encapsulation in JavaScript

Encapsulation is a fundamental concept in OOP that involves bundling the data (properties) and the methods that operate on that data into a single unit (class) and restricting direct access to some of an object's components. In JavaScript, encapsulation is achieved using private and public fields.

Public and Private Fields

ES6 introduced private and public fields, allowing you to define the accessibility of class fields and methods.

######## Public Fields

Public fields are accessible from anywhere.

class Vehicle {
  constructor(type) {
    this.type = type; // Public field
  }

  displayType() {
    return `This is a ${this.type}`;
  }
}

const myVehicle = new Vehicle('Bike');
console.log(myVehicle.displayType()); // Output: This is a Bike
console.log(myVehicle.type); // Output: Bike

######## Private Fields

Private fields are only accessible within the class itself.

class Vehicle {
  ##type; // Private field

  constructor(type) {
    this.##type = type; // Accessible only within the class
  }

  displayType() {
    return `This is a ${this.##type}`;
  }
}

const myVehicle = new Vehicle('Bike');
console.log(myVehicle.displayType()); // Output: This is a Bike
console.log(myVehicle.##type); // Uncaught SyntaxError: Private field '##type' must be declared in an enclosing class
Explanation
  • Private Fields: Private fields are denoted by a ## symbol. They can only be accessed within the class they are declared in.
  • Public Fields: Public fields can be accessed and modified from outside the class.

Polymorphism in JavaScript

Polymorphism allows objects to be treated as instances of their parent class, enabling one interface to represent different underlying forms (data types). JavaScript's dynamic typing and prototype-based inheritance make polymorphism easy to implement.

Example of Polymorphism
class Bird {
  fly() {
    return 'Flying...';
  }
}

class Penguin extends Bird {
  fly() {
    return 'Cannot fly, but swimming...';
  }
}

function makeAnimalFly(animal) {
  console.log(animal.fly());
}

const myBird = new Bird();
const myPenguin = new Penguin();

makeAnimalFly(myBird); // Output: Flying...
makeAnimalFly(myPenguin); // Output: Cannot fly, but swimming...
Explanation
  • Overriding Methods: The fly method in the Penguin class overrides the method in the Bird class.
  • Dynamic Method Invocation: The makeAnimalFly function can accept any object that has a fly method, demonstrating polymorphism.

Practical Example: Building a Library System

Let's create a simple library system to demonstrate encapsulation, inheritance, and polymorphism.

Step 1: Define the Base Class

class Book {
  ##title;
  ##author;
  ##yearPublished;

  constructor(title, author, yearPublished) {
    this.##title = title;
    this.##author = author;
    this.##yearPublished = yearPublished;
  }

  getTitle() {
    return this.##title;
  }

  getAuthor() {
    return this.##author;
  }

  getYearPublished() {
    return this.##yearPublished;
  }
}

Step 2: Create Subclass for EBooks

class EBook extends Book {
  ##format;

  constructor(title, author, yearPublished, format) {
    super(title, author, yearPublished); // Call the parent class constructor
    this.##format = format;
  }

  getDetails() {
    return `${super.getTitle()} by ${super.getAuthor()} (${super.getYearPublished()}), Format: ${this.##format}`;
  }
}

Step 3: Create Subclass for Physical Books

class PhysicalBook extends Book {
  ##isbn;

  constructor(title, author, yearPublished, isbn) {
    super(title, author, yearPublished);
    this.##isbn = isbn;
  }

  getDetails() {
    return `${super.getTitle()} by ${super.getAuthor()} (${super.getYearPublished()}), ISBN: ${this.##isbn}`;
  }
}

Step 4: Implementing Polymorphism

function displayBookDetails(book) {
  console.log(book.getDetails());
}

const eBook = new EBook('JavaScript Essentials', 'John Doe', 2023, 'EPUB');
const physicalBook = new PhysicalBook('JavaScript Essentials', 'John Doe', 2023, '978-3-16-148410-0');

displayBookDetails(eBook); // Output: JavaScript Essentials by John Doe (2023), Format: EPUB
displayBookDetails(physicalBook); // Output: JavaScript Essentials by John Doe (2023), ISBN: 978-3-16-148410-0
Explanation
  • Base Class: Book is the base class with private fields and getter methods.
  • Subclasses: EBook and PhysicalBook inherit from Book and provide specific implementations of the getDetails method.
  • Function Polymorphism: The displayBookDetails function can accept any object that has a getDetails method, demonstrating polymorphism.

Practical Example: Shape Hierarchy

Let's create a simple shape hierarchy to demonstrate inheritance and polymorphism.

Step 1: Define the Base Class

class Shape {
  ##color;

  constructor(color) {
    this.##color = color;
  }

  getColor() {
    return this.##color;
  }

  area() {
    throw new Error('Area method must be implemented');
  }
}

Step 2: Create Subclasses for Different Shapes

class Circle extends Shape {
  ##radius;

  constructor(color, radius) {
    super(color);
    this.##radius = radius;
  }

  area() {
    return Math.PI * this.##radius * this.##radius;
  }
}

class Rectangle extends Shape {
  ##width;
  ##height;

  constructor(color, width, height) {
    super(color);
    this.##width = width;
    this.##height = height;
  }

  area() {
    return this.##width * this.##height;
  }
}

Step 3: Implementing Polymorphism

function printShapeDetails(shape) {
  console.log(`This is a ${shape.getColor()} shape with an area of ${shape.area().toFixed(2)}`);
}

const circle = new Circle('red', 5);
const rectangle = new Rectangle('blue', 4, 6);

printShapeDetails(circle); // Output: This is a red shape with an area of 78.54
printShapeDetails(rectangle); // Output: This is a blue shape with an area of 24.00
Explanation
  • Base Class: Shape is the base class with a private field color and abstract area method.
  • Subclasses: Circle and Rectangle inherit from Shape and provide specific implementations of the area method.
  • Polymorphism: The printShapeDetails function can accept any object that has a getColor and area method.

Benefits of JavaScript OOP

  • Reusability: Inheritance allows you to create subclasses that inherit properties and methods from parent classes, promoting code reuse.
  • Maintainability: Encapsulation allows you to hide complex implementation details, making the code easier to maintain.
  • Scalability: Polymorphism allows you to write more flexible and scalable code by defining a common interface for objects.

Best Practices in JavaScript OOP

  • Use Descriptive Names: Choose meaningful class and method names to make your code easy to understand.
  • Minimize Direct Access to Data: Use private fields to prevent accidental modification of data.
  • Favor Composition Over Inheritance: Composition allows you to combine objects to achieve functionality, which often makes your code more flexible and maintainable than inheritance.
  • Document Your Code: Proper documentation helps other developers (and yourself) understand how to use and maintain the code.

Common Mistakes to Avoid

  • Overusing Inheritance: Inheritance can lead to deeply nested class hierarchies, which can be difficult to maintain. Use inheritance judiciously and consider composition when appropriate.
  • Ignoring Encapsulation: Overexposing your object's data can lead to fragile code that is hard to maintain. Use private fields and provide controlled access through public methods.
  • Underestimating Polymorphism: Polymorphism can make your code more flexible and easier to extend. Understand when and how to use it to take full advantage of its benefits.

Real-World Applications of JavaScript OOP

  • Single Page Applications (SPAs): Frameworks like React and Angular implement OOP principles to manage the state and UI of web applications.
  • Game Development: OOP is widely used in game development to create game characters, levels, and other game entities.
  • Enterprise Applications: Large applications often require a structured approach to management and organization, making OOP an excellent choice.

Conclusion

In this blog, we explored the fundamentals of Object-Oriented Programming in JavaScript, including classes, inheritance, encapsulation, and polymorphism. We created practical examples and discussed common mistakes to avoid and best practices to follow. OOP is a powerful paradigm that can greatly enhance your JavaScript programming by promoting modular, reusable, and maintainable code. By understanding and applying OOP principles, you can write more efficient and flexible JavaScript applications.

Resources

For further learning and advanced topics in JavaScript OOP, check out the resources above. They provide detailed explanations and examples to deepen your understanding of JavaScript's object-oriented capabilities.