[Advanced JS] Object-Oriented Programming (OOP) in JavaScript: Concepts & Examples

You’ve probably heard of "Object-Oriented Programming" or OOP, a foundational concept in the world of software development. But what about OOP in JavaScript? Is a flexible, multi-paradigm language like JavaScript truly "object-oriented"? The answer is yes, and mastering OOP in JavaScript not only helps you write cleaner, more maintainable code, but also opens the door to building complex, large-scale applications efficiently.

Object-Oriented Programming (OOP) in JavaScript

This article will help you explore the world of OOP in JavaScript in the most intuitive and easy-to-understand way, from basic concepts to practical examples.

What is Object-Oriented Programming (OOP)?

Object-Oriented Programming is a programming paradigm based on the concept of "objects". Imagine every real-world object (like a car, a person, a house) has its own properties (attributes) and actions (methods). OOP models this exactly in the world of code.

What is OOP?

  • Object: A concrete instance that contains both data (properties) and behaviors (methods) to manipulate that data. For example, an object car may have properties like color, brand and methods like drive(), stop().
  • Class: A blueprint or template for creating objects. From a Car class, we can create many different car objects. Since ES6, JavaScript officially introduced the class syntax, making this more familiar to developers from other languages.

The main goal of OOP is to encapsulate data and the functions that operate on that data in one place, making code more organized, manageable, and highly reusable.

Core Principles of OOP - Applied in JavaScript

Object-Oriented Programming is built on four main principles. Understanding these is key to applying OOP effectively in your JavaScript projects.

4 core principles of OOP

1. Encapsulation 💊

This principle bundles data and the methods that operate on that data into a single object, while hiding complex implementation details inside. This protects data from unwanted access and modification from outside.

In JavaScript, we can achieve encapsulation through closures or by using private fields with the # syntax in ES2022.

Example: Using Private Fields (#).

class Phone {
  #password // This is a private field

  constructor(name, password) {
    this.name = name
    this.#password = password
  }

  // Public method to check password
  checkPassword(inputPassword) {
    if (inputPassword === this.#password) {
      console.log('Unlocked successfully!')
      return true
    } else {
      console.log('Wrong password!')
      return false
    }
  }
}

const iphone = new Phone('iPhone 15', '123456')
console.log(iphone.name) // "iPhone 15"
// console.log(iphone.#password); // Error! Cannot access from outside

iphone.checkPassword('111111') // Wrong password!
iphone.checkPassword('123456') // Unlocked successfully!

2. Abstraction 🎭

Abstraction focuses on exposing only the necessary features of an object to the outside and hiding complex internal details. Like when you drive a car, you only care about the steering wheel, gas pedal, and brake, not how the engine or pistons work.

In JavaScript, abstraction is achieved by creating a simple interface (public methods) to interact with the object, while complex logic is hidden.

Example: Hiding complex logic.

class CoffeeMachine {
  #clean() {
    console.log('Auto cleaning the machine...')
  }

  #boilWater() {
    console.log('Boiling water...')
  }

  brewCoffee() {
    this.#boilWater()
    console.log('Grinding coffee beans...')
    console.log('Brewing Espresso coffee!')
    this.#clean()
  }
}

const machine = new CoffeeMachine()
machine.brewCoffee() // User only needs to call this method
// They cannot call machine.#boilWater() or machine.#clean()

3. Inheritance 👨‍👩‍👧

Inheritance allows a class (child class) to inherit properties and methods from another class (parent class). This maximizes code reuse and creates a logical hierarchy between classes.

JavaScript uses prototype-based inheritance, but the ES6 class syntax provides a more familiar way to do this using extends and super.

Example: Familiar syntax with class, extends, and super.

// Parent class
class Animal {
  constructor(name) {
    this.name = name
  }

  eat() {
    console.log(`${this.name} is eating.`)
  }
}

// Child class inherits from Animal
class Dog extends Animal {
  constructor(name, breed) {
    super(name) // Call parent constructor
    this.breed = breed
  }

  bark() {
    console.log('Woof! Woof!')
  }
}

const milu = new Dog('Milu', 'Corgi')
milu.eat() // "Milu is eating." (Inherited from Animal)
milu.bark() // "Woof! Woof!" (Dog's own method)

4. Polymorphism 🦎

"Poly" means many, and "morph" means forms. Polymorphism is the ability of different objects to respond to the same message (method call) in different ways. This allows us to write flexible and extensible code.

Polymorphism is often shown through method overriding, where a child class provides a specific implementation for a method already defined in the parent class.

Example:

class Shape {
  draw() {
    console.log('Drawing a generic shape.')
  }
}

class Circle extends Shape {
  // Override draw() method
  draw() {
    console.log('Drawing a circle 🔵.')
  }
}

class Square extends Shape {
  // Override draw() method
  draw() {
    console.log('Drawing a square 🟪.')
  }
}

function drawShape(shape) {
  shape.draw()
}

const circle = new Circle()
const square = new Square()

drawShape(circle) // "Drawing a circle 🔵."
drawShape(square) // "Drawing a square 🟪."

In the example above, the drawShape function can work with any object that is a Shape or inherits from it, and the call to shape.draw() will automatically execute the correct method for the specific object passed in.

Why Use OOP in JavaScript?

Applying OOP principles to your JavaScript projects brings many practical benefits:

  • Better code organization: OOP helps structure large projects, breaking them into manageable objects, making code clearer and easier to understand.
  • Code reuse: With inheritance, you can reuse properties and methods from existing classes, reducing code duplication and saving development time.
  • Easier maintenance and extension: Thanks to encapsulation, changing logic inside an object won’t affect other parts of the application. Adding new features is also simpler by creating new classes that inherit from existing ones.
  • Modeling real-world problems: OOP lets you model real-world problems as objects in code, making problem-solving more natural and intuitive.

Conclusion: OOP is a Methodical and Effective Mindset

Object-Oriented Programming is not just a set of rules, but a mindset—a methodical approach to building software efficiently and effectively.

Although JavaScript is a multi-paradigm language, applying OOP, especially with the modern ES6 class syntax, equips you with a powerful tool to solve complex problems, build robustly structured applications, and prepare for future scalability. Start applying these principles to your next project and feel the difference!

Related Posts

[JS Basics] Objects in JavaScript: Concepts & Effective Usage

A deep dive into how Objects work in JavaScript. Detailed guide on properties, methods, and how to use Objects to write clean and efficient code.

[Advanced JS] What is the Event Loop? Discover How JavaScript Works

The Event Loop is the heart of JavaScript, but not everyone understands it well. This article visually explains how the Event Loop works and related concepts.

[JS Basics] Asynchronous Programming in JavaScript: Callback, Promise, Async/Await

Learn asynchronous programming in JavaScript with practical examples of Async/Await, Promise, and Callback. This article helps you master the concepts, avoid common mistakes, and optimize code performance.

[Advanced JS] Intersection Observer API: The Complete Guide to Effective Usage

A comprehensive guide to the Intersection Observer API, from theory to practice. Understand how this API works and apply it to real-world projects to improve user experience.