DEV Community

Neha Sharma
Neha Sharma

Posted on • Originally published at nehasharma.dev

Object-Oriented JavaScript: A Practical Guide with Examples

If you’ve been programming for a decade or you’re just getting started, you’ve probably heard of OOP (Object-Oriented Programming). It’s a powerful paradigm that support many modern programming languages, from Java to C#.

But when it comes to JavaScript, things are interesting. JavaScript wasn’t originally built with classes in mind—it was designed as a lightweight scripting language for browsers. Over time, as web applications became more complex, JavaScript evolved. Today, with ES6 and beyond, it supports OOP-like patterns while still being fundamentally prototype-based under the hood.

In this blog, we’ll explore:

1 . What OOP is

2 . The core concepts of OOP

3 . How OOP is implemented in JavaScript

4 . A real-world example you probably already use

What is OOP?

Object-Oriented Programming is a paradigm that organizes code around objects. An object represents a real-world entity (like a user, product, or order) and bundles both data (properties) and behavior (methods) together.

In programming, we treat every thing :

As a JavaScript developer, you can think of an object as a collection of related data and behavior.

An object is made up of:

1 . Properties (data): key–value pairs (like variables inside the object).

2 . Methods (behavior): functions stored as properties, which define what the object can do.

In simple terms: A property describes what it has and a method describes what it does.

Benefits of OOP:

1 . Reusability: Write once, use across projects.

2 . Encapsulation: Hide implementation details, expose only what matters.

3 . Maintainability: Easier to update and extend.

4 . Abstraction: Focus on “what” an object does, not “how” it does it.

5 . Polymorphism: Same method name, different behavior depending on context.

Concpets in OOP

1 . Object

2 . Class & constructors

3 . Inheritance

4 . Polymorphism

5 . Abstraction

6 . Encapsulation

JavaScript and OOPs concept

In Modern JavaScript, we can implement OOP concepts. As an engineer, you should have the goal of using OOP for various benefits. Eg: for reusability, encapsulation, inheritance, and so on. Remember these are the programming goals you should as an progarmmer you should have goals.

Do we need to implement all these concepts? No..but as needed.

1. Object

Objects are the foundation of OOP. In JavaScript, before modern JavaScript we used to refer JavaScript as prototypal scripting language. As everything used to be done through prototype. In JavaScript, we make an object like this. Now, from JS pov, why this is important. As it helps in have a strucuture your code , data, properties and methods.

// creating an object in JavaScript

const car = {
  brand: "Tesla", // data
  model: "Model 3", // data 
  drive() { // method 
    console.log(`${this.brand} ${this.model} is driving...`);
  }
};

car.drive(); // Tesla Model 3 is driving...
Enter fullscreen mode Exit fullscreen mode

The “Car” example explains OOP well, but it doesn’t feel very connected to everyday JavaScript work. So let’s model something practical—a carousel. In JavaScript, we can represent a carousel as an object.

// Creating a Carousel using object

const Carousel = {
  images: ["image1.jpg", "image2.jpg", "image3.jpg"], 
  style: "default",                                   

  createCarousel() {                                  
    console.log("Carousel created with images:", this.images);
  },

  nextImage(index) {
    console.log("Showing:", this.images[index % this.images.length]);
  }
};

// Using the object
Carousel.createCarousel(); 

Enter fullscreen mode Exit fullscreen mode

2 . Classes & Constructors

Before ES6, JavaScript didn’t have the class or constructor keywords.

Developers who came from languages like Java or PHP often mimicked these concepts using functions and prototypes.

With ES6, JavaScript introduced the class syntax.

It doesn’t add new functionality under the hood — classes are syntactic sugar over prototypes — but it makes object-oriented code more readable and familiar.

The constructor is a special method that runs automatically when you create a new instance of a class.

Class and constructors form the foundation for the rest of the OOP concepts we’ll cover — from inheritance to abstraction.


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

  drive() {
    console.log(`${this.brand} ${this.model} is driving...`);
  }
}

const myCar = new Car("Tesla", "Model Y");
myCar.drive(); // Tesla Model Y is driving...
Enter fullscreen mode Exit fullscreen mode

Here, Car is a class, and the constructor sets up its properties. When we call new Car("Tesla", "Model Y"), the constructor runs automatically.

class Carousel {
  constructor(items = []) {
    this.items = items;
    this.currentIndex = 0;
  }

  showCurrent() {
    console.log(`Showing: ${this.items[this.currentIndex]}`);
  }

  next() {
    this.currentIndex = (this.currentIndex + 1) % this.items.length;
    this.showCurrent();
  }

  prev() {
    this.currentIndex =
      (this.currentIndex - 1 + this.items.length) % this.items.length;
    this.showCurrent();
  }

  goTo(index) {
    if (index >= 0 && index < this.items.length) {
      this.currentIndex = index;
      this.showCurrent();
    } else {
      console.log("Invalid index");
    }
  }
}

const carousel = new Carousel(["img1.jpg", "img2.jpg", "img3.jpg"]);
carousel.showCurrent(); 
carousel.next();        
carousel.goTo(2);      

Enter fullscreen mode Exit fullscreen mode

Here, Carousel organizes state (items, currentIndex) and exposes methods to control the slideshow. The constructor ensures every new carousel starts in a valid state.

Classes and constructors in ES6 make object-oriented JavaScript cleaner and more intuitive, while still being powered by the prototype system under the hood.

3 . Inheritance (Prototypes)

Inheritance allows a child class to reuse and extend functionality from a parent class. It supports the DRY (Don’t Repeat Yourself) principle by letting you share code instead of duplicating it.

In JavaScript, inheritance was originally implemented using prototypes. Since ES6, we have the class syntax, which makes inheritance much more intuitive though under the hood, it still uses prototypes.

class Vehicle {
  constructor(type) {
    this.type = type;
  }
  start() {
    console.log(`${this.type} is starting...`);
  }
}

class Car extends Vehicle {
  constructor(brand) {
    super("Car"); // call parent constructor
    this.brand = brand;
  }

  start() { // method overriding
    console.log(`${this.brand} ${this.type} is revving up!`);
  }
}

const myCar = new Car("Tesla");
myCar.start(); // Tesla Car is revving up!

Enter fullscreen mode Exit fullscreen mode

Here, Car inherits from Vehicle and reuses its constructor logic. It also overrides the start() method to provide custom behavior.

Suppose we have a base Carousel class. We can extend it to create specialized carousels with extra features like styles or auto-scroll.

class Carousel {
  constructor(items = []) {
    this.items = items;
    this.currentIndex = 0;
  }

  showCurrent() {
    console.log(`Showing: ${this.items[this.currentIndex]}`);
  }

  next() {
    this.currentIndex = (this.currentIndex + 1) % this.items.length;
    this.showCurrent();
  }
}

class ImageCarousel extends Carousel {
  constructor(images = [], style = "default") {
    super(images); // call parent constructor
    this.style = style;
  }

  showCurrent() { // override method
    console.log(
      `Displaying image [${this.style} style]: ${this.items[this.currentIndex]}`
    );
  }
}

const imgCarousel = new ImageCarousel(
  ["img1.jpg", "img2.jpg", "img3.jpg"],
  "dark"
);

imgCarousel.showCurrent(); 
imgCarousel.next();        
Enter fullscreen mode Exit fullscreen mode

Here, ImageCarousel extends Carousel, reusing its logic for navigation, but overrides showCurrent() to add styling context.

Inheritance lets you reuse and extend base functionality. Overriding allows subclasses to adapt behavior to their needs.
Even with ES6 classes, JavaScript’s inheritance model is still prototype based under the hood.

4 . Polymorphism

Polymorphism allows different classes to define their own versions of the same method. In practice, a child class can override a method from its parent class to provide specialized behavior.

Polymorphism and inheritance often go hand in hand:

  • Inheritance lets you reuse a base structure (VehicleCar, Bike).

  • Polymorphism lets each subclass override behavior to suit its needs.

If you’re ever confused: Inheritance = reusing code and Polymorphism = redefining behavior.

class Vehicle {
  drive() {
    console.log("This vehicle is moving...");
  }
}

class Car extends Vehicle {
  drive() {
    console.log("Car is driving on the road...");
  }
}

class Bike extends Vehicle {
  drive() {
    console.log("Bike is zooming through traffic...");
  }
}

const myCar = new Car();
const myBike = new Bike();

myCar.drive(); // Car is driving on the road...
myBike.drive(); // Bike is zooming through traffic...

Enter fullscreen mode Exit fullscreen mode

In our caraousel example

class Carousel {
  show() {
    console.log("Showing carousel item...");
  }
}

class ImageCarousel extends Carousel {
  show() {
    console.log("Displaying an image slide...");
  }
}

class VideoCarousel extends Carousel {
  show() {
    console.log("Playing a video slide...");
  }
}

const imgCarousel = new ImageCarousel();
const vidCarousel = new VideoCarousel();

imgCarousel.show(); // Displaying an image slide...
vidCarousel.show(); // Playing a video slide...

Enter fullscreen mode Exit fullscreen mode

Here, both ImageCarousel and VideoCarousel extend the same Carousel base class, but each redefines the show() method — this is polymorphism in action.

5. Abstraction

Abstraction is about hiding implementation details and exposing only what’s necessary. It lets you define a contract (what methods must exist) without forcing subclasses to care about how they’re implemented.

JavaScript doesn’t have native abstract classes like Java or C#, but we can simulate abstraction by:

  • Throwing errors in base methods that must be overridden.

  • Using documentation or patterns to enforce a contract.

class Vehicle {
  start() {
    throw new Error("Method 'start()' must be implemented");
  }
}

class Car extends Vehicle {
  start() {
    console.log("Car engine started...");
  }
}

class Bike extends Vehicle {
  start() {
    console.log("Bike engine started...");
  }
}

const myCar = new Car();
const myBike = new Bike();

myCar.start(); // Car engine started...
myBike.start(); // Bike engine started...


Enter fullscreen mode Exit fullscreen mode

Here, Vehicle enforces that every subclass must define start(), but the actual implementation is left to Car and Bike.

class BaseCarousel {
  render() {
    throw new Error("Method 'render()' must be implemented");
  }
}

class ImageCarousel extends BaseCarousel {
  render() {
    console.log("Rendering image carousel...");
  }
}

class VideoCarousel extends BaseCarousel {
  render() {
    console.log("Rendering video carousel...");
  }
}

const imgCarousel = new ImageCarousel();
const vidCarousel = new VideoCarousel();

imgCarousel.render(); // Rendering image carousel...
vidCarousel.render(); // Rendering video carousel...

Enter fullscreen mode Exit fullscreen mode

Here, BaseCarousel defines the abstract method render(). Every type of carousel must implement it, but how it renders (images, videos, etc.) is up to the subclass.

6. Encapsulation

Encapsulation is about hiding internal details and exposing only what’s necessary through a public API. It prevents direct access to an object’s state, which makes code more secure, modular, and maintainable.

In modern JavaScript (ES2022+), we can achieve encapsulation using private fields (denoted by #), getters, and setters.

class Car {
  #fuel = 0; // private field

  refuel(liters) {
    this.#fuel += liters;
    console.log(`Added ${liters}L of fuel.`);
  }

  drive() {
    if (this.#fuel > 0) {
      this.#fuel--;
      console.log("Car is driving...");
    } else {
      console.log("No fuel left!");
    }
  }

  getFuelLevel() {
    return this.#fuel;
  }
}

const myCar = new Car();
myCar.refuel(2);
myCar.drive(); // Car is driving...
console.log(myCar.getFuelLevel()); // 1
// myCar.#fuel (Error: private field)

Enter fullscreen mode Exit fullscreen mode

The car’s fuel state is hidden. You can only change it through refuel() and check it with getFuelLevel().

class Carousel {
  #currentIndex = 0; // private field

  constructor(items = []) {
    this.items = items;
  }

  next() {
    this.#currentIndex = (this.#currentIndex + 1) % this.items.length;
    this.show();
  }

  prev() {
    this.#currentIndex =
      (this.#currentIndex - 1 + this.items.length) % this.items.length;
    this.show();
  }

  show() {
    console.log(`Showing: ${this.items[this.#currentIndex]}`);
  }

  getCurrentIndex() {
    return this.#currentIndex;
  }
}

const carousel = new Carousel(["img1.jpg", "img2.jpg", "img3.jpg"]);
carousel.next(); // Showing: img2.jpg
console.log(carousel.getCurrentIndex()); // 1
// carousel.#currentIndex ❌ (Error: private field)

Enter fullscreen mode Exit fullscreen mode

The carousel’s internal state (#currentIndex) is private. Consumers can only change slides via next() / prev() and can’t tamper with the internals directly.

Encapsulation protects internal state and provides a clear, safe API for interaction. This reduces bugs and makes codebases easier to evolve without breaking consumers.

Bringing It All Together

Let’s combine what we’ve learned so far: classes, constructors, inheritance, and polymorphism into one example.

class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, I’m ${this.name}`);
  }
}

class Admin extends User {
  greet() { // method overriding (polymorphism)
    console.log(`👋 Admin ${this.name} here. I manage the system.`);
  }
}

const normalUser = new User("Alice");
const adminUser = new Admin("Bob");

normalUser.greet(); // Hello, I’m Alice
adminUser.greet();  // 👋 Admin Bob here. I manage the system.

Enter fullscreen mode Exit fullscreen mode

Real world example

If you’ve worked with React, you’ve already used OOP principles. Before hooks become popular, we were using OOPs to make class based components. If you are someone who is working in building libraries or polyfills then you will be using OOPs a lot.

import React from "react";

// Base Component
class Button extends React.Component {
  render() {
    return <button>{this.props.label}</button>;
  }
}

// Specialized Component (Inheritance + Polymorphism)
class IconButton extends Button {
  render() {
    return (
      <button>
        <span role="img" aria-label="icon">
          🔍
        </span>{" "}
        {this.props.label}
      </button>
    );
  }
}

export default function App() {
  return (
    <div>
      <Button label="Click Me" />
      <IconButton label="Search" />
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Summary

OOPs in JavaScript are important to write clean and maintainable code. OOPs also promotes Solid prinicples of programming.

OOP concept What/Why
Object A collection of related data (properties) and behavior (methods). Organizes code into real-world entities. Promotes Single Responsiblity Model (SRE) and Keep it simple silly (KISS)
Class & Constructor A blueprint for creating objects. The constructor sets initial state when creating an instance.Provides structure, consistency, and easier instantiation of objects.
Inheritance A child class reuses and extends functionality from a parent class. Promotes DRY principle, code reuse, and logical hierarchies.
Polymorphism Child classes override or redefine parent methods with their own behavior. Flexibility: same method name, different outcomes depending on context.Promotes Open/Closed Principle
Abstraction Hides implementation details and exposes only required functionality. Simulated in JS via base classes that throw errors. Defines clear contracts while allowing flexible implementations. Promotes KISS, and Interface Segregation Principle
Encapsulation Restricting direct access to internal state; exposing it through public methods.Improves maintainability, security, and prevents unintended interference. Promotes SRP, Low Coupling / High Cohesion

I hope this help you to write your clean, and better code in your next project. If you like it then do share to your social media - X, Linkedin.

Top comments (0)