DEV Community

Cover image for Is JavaScript Object Oriented Programming?
Mateen Kiani
Mateen Kiani

Posted on • Originally published at milddev.com

Is JavaScript Object Oriented Programming?

JavaScript has changed a lot since its early days, but one thing has stayed true: it still relies on objects. We often talk about functions, callbacks, and promises, yet we skip over how deep the object system really goes. Can JavaScript offer the same object oriented structure we see in classic languages like Java or C#?

It can, once you see how prototype chains, constructor functions, and ES6 classes fit together. By learning how these parts interact, you can write cleaner code, prevent hidden bugs, and choose the right approach for your project. The next sections will guide you from basic objects to advanced design patterns, so you feel confident building any feature with JavaScript.

Basics of JavaScript Objects

At its core, JavaScript is built around objects. An object is a collection of key-value pairs where keys are strings or symbols. You can create an object in several ways:

  • Using an object literal, for example:
const user = { name: 'Alice', age: 30 }
Enter fullscreen mode Exit fullscreen mode
  • Using Object.create, which sets up the prototype:
const base = { greet() { return 'hi' } }
const person = Object.create(base)
person.name = 'Bob'
Enter fullscreen mode Exit fullscreen mode

Tip: You can convert an object to a JSON string or parse JSON into an object. See more on JSON.

Objects hold data and behavior. Each object has an internal link to a prototype. This link, called [[Prototype]], determines what properties and methods are available if you don't define them directly on the object.

Understanding this link is key before you move on to classes. It also explains why adding or changing a method on one object can affect others created from the same prototype.

Prototypes and Inheritance

JavaScript uses prototype based inheritance instead of classes at a low level. When you access a property, the engine looks on the object itself. If it does not find it, it follows the prototype chain. This chain can go on until it hits Object.prototype or null.

Here is how prototype inheritance works:

function Animal(type) {
  this.type = type
}
Animal.prototype.speak = function() {
  return `I am a ${this.type}`
}

const dog = new Animal('dog')
console.log(dog.speak()) // I am a dog
Enter fullscreen mode Exit fullscreen mode

In this example, speak is not on dog, but on Animal.prototype. The new keyword links dog to that prototype. This setup defines shared behavior without copying functions to each instance.

Tip: Use Object.getPrototypeOf(obj) to inspect an object’s prototype at runtime. It helps debug inheritance chains.

Prototype chains are flexible. You can swap prototypes, extend built ins, or even create custom chains. But with great power comes great responsibility. If you overuse prototypes, your code can become hard to follow.

ES6 Classes Overview

With ES6, JavaScript introduced a class syntax that looks more familiar to developers from classical OOP languages. Under the hood, it still uses prototypes, but the code reads cleaner:

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

  greet() {
    return `Hello, ${this.name}`
  }
}

const u = new User('Eve')
console.log(u.greet()) // Hello, Eve
Enter fullscreen mode Exit fullscreen mode

Classes let you define constructors and methods in one place. You can also use extends and super for inheritance:

class Admin extends User {
  constructor(name, role) {
    super(name)
    this.role = role
  }

  getRole() {
    return this.role
  }
}
Enter fullscreen mode Exit fullscreen mode

Tip: Even with classes, remember prototypes drive the actual inheritance. You can still use Object.setPrototypeOf if you need to tweak links.

ES6 classes group related logic and make patterns like factory or singleton easier to read. They also work nicely with tools like Babel and TypeScript.

Encapsulation and Privacy

One common OOP idea is to hide internal details. JavaScript did not offer private properties until recently. Now you can use private fields with the # syntax:

class Counter {
  #count = 0

  increment() {
    this.#count++
  }

  getCount() {
    return this.#count
  }
}

const c = new Counter()
c.increment()
console.log(c.getCount()) // 1
Enter fullscreen mode Exit fullscreen mode

Before private fields, developers used closures and modules to hide data:

function createBankAccount() {
  let balance = 0
  return {
    deposit(amount) {
      balance += amount
    },
    getBalance() {
      return balance
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Tip: Use modules or closures for older environments that do not support private fields.

Encapsulation prevents outside code from changing internal state in unexpected ways. It leads to more robust APIs and safer libraries.

Design Patterns in JS

Design patterns give you a template for common problems. In JavaScript, you can adapt patterns from other languages or invent new ones. Here are a few:

  • Factory Pattern: Create objects without exposing new.
  • Singleton Pattern: Ensure only one instance of a class exists.
  • Module Pattern: Encapsulate related functions and state.
  • Observer Pattern: Let objects subscribe to events and get notified.
// Module pattern example
const Logger = (function() {
  let logs = []
  function log(message) {
    logs.push({ message, timestamp: Date.now() })
  }
  function getLogs() {
    return logs
  }
  return { log, getLogs }
})()

Logger.log('start')
console.log(Logger.getLogs())
Enter fullscreen mode Exit fullscreen mode

These patterns make code predictable and easier to maintain. They also let teams share a common vocabulary.

Tip: Choose a pattern that fits your project size and team. Avoid overengineering small scripts.

OOP in Modern Frameworks

Many frameworks let you use OOP principles. For example, in React you can write class components:

import React, { Component } from 'react'

class App extends Component {
  render() {
    return <div>Welcome {this.props.name}</div>
  }
}
Enter fullscreen mode Exit fullscreen mode

But hooks and functions are now more common. Even then, you can group logic into custom hooks to mimic encapsulation:

import { createContext, useContext, useState } from 'react'

const AuthContext = createContext()
export function AuthProvider({ children }) {
  const [user, setUser] = useState(null)
  return (
    <AuthContext.Provider value={{ user, setUser }}>
      {children}
    </AuthContext.Provider>
  )
}
export function useAuth() {
  return useContext(AuthContext)
}
Enter fullscreen mode Exit fullscreen mode

Tip: See more on React Context for a deep dive on context.

Frameworks like Angular or Vue use classes, decorators, or reactive data. OOP ideas help organize components, services, and modules into coherent systems.

Conclusion

JavaScript is truly object oriented at its heart. With prototypes as the base, ES6 classes offer a friendly syntax, and private fields or closures bring encapsulation. Design patterns help you structure code and frameworks let you apply OOP to real apps. When you understand how each piece works, you can avoid traps like unexpected prototype changes or leaky data. Start with object literals, learn the prototype chain, and then explore classes. Try encapsulating logic in modules or hooks. With these tools in hand, you can build robust, maintainable code. Embrace JavaScript OOP and see your projects become clearer and more scalable.

Top comments (0)