Complete JavaScript Course Module 13: Advanced: Modern JavaScript Patterns

Classes and Object-Oriented JavaScript

  • Beginner
  • Lesson 13.2
  • 5 min read
  • 58 of 76 in the course

In short

JavaScript classes are blueprints for creating many similar objects. Learn class syntax, constructors, getters, static and private fields, and inheritance. What is a class in JavaScript? A class is a blueprint, like a cookie cutter.

JavaScript classes are blueprints for creating many similar objects. Learn class syntax, constructors, getters, static and private fields, and inheritance.

What is a class in JavaScript?

A class is a blueprint, like a cookie cutter. You design the shape once, and then you can make as many cookies (objects) as you want. Each object made from a class is called an instance.

This style of organizing code around objects that hold data and the actions on that data is called object-oriented programming (OOP).

How to write a class with a constructor and methods

The constructor is a special method that runs when you create an instance with new. Inside it, this means "the new object being built". Methods are functions that every instance can use.

class BankAccount {
  constructor(owner, balance = 0) {
    this.owner = owner;     // save data on the new object
    this.balance = balance;
  }

  deposit(amount) {
    this.balance += amount;
    return `${this.owner} now has $${this.balance}`;
  }
}

const maya = new BankAccount("Maya", 100);
const leo = new BankAccount("Leo");

console.log(maya.deposit(50)); // Output: Maya now has $150
console.log(leo.deposit(20));  // Output: Leo now has $20

maya and leo are two separate objects with their own data, but they share the same deposit method. Class names start with a capital letter by convention.

Getters, setters, static members and private fields

Classes have a few extra tools:

  • A getter (get) looks like a property but runs a function when you read it.
  • A setter (set) runs a function when you assign a value, so you can check it first.
  • A static member belongs to the class itself, not to instances. It is good for helpers and counters.
  • A private field starts with #. It can only be used inside the class. Code outside cannot read or change it.
class Product {
  static count = 0; // shared by the class
  #price;           // private field

  constructor(name, price) {
    this.name = name;
    this.price = price; // goes through the setter below
    Product.count++;
  }

  get price() {
    return this.#price;
  }

  set price(value) {
    if (value < 0) throw new Error("Price cannot be negative");
    this.#price = value;
  }

  get label() {
    return `${this.name} - $${this.#price.toFixed(2)}`;
  }

  static compare(a, b) {
    return a.price - b.price;
  }
}

const mug = new Product("Mug", 8);
const lamp = new Product("Lamp", 25);

console.log(mug.label);         // Output: Mug - $8.00
mug.price = 10;
console.log(mug.price);         // Output: 10
console.log(Product.count);     // Output: 2
console.log([lamp, mug].sort(Product.compare).map((p) => p.name)); // Output: ["Mug", "Lamp"]

try {
  mug.price = -5;
} catch (error) {
  console.log(error.message);   // Output: Price cannot be negative
}
// console.log(mug.#price);     // SyntaxError: private field

Notice that label and price are used without (). That is what makes getters feel like normal properties. The #price field protects the data: the only way to change it is through the setter, which checks the value.

Note: Private # fields are real privacy, enforced by the language. An older habit was to start names with _ (like _price), but that is only a hint; anyone can still change it.

How does inheritance work with extends and super?

Inheritance lets one class build on another. The new class (the child) gets all the methods of the old class (the parent) and can add or change some. Use extends to inherit and super to call the parent.

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

  speak() {
    return `${this.name} makes a sound.`;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);        // run the Animal constructor first
    this.breed = breed;
  }

  speak() {             // override the parent method
    return `${super.speak()} Woof!`;
  }
}

const rex = new Dog("Rex", "Beagle");
console.log(rex.speak());            // Output: Rex makes a sound. Woof!
console.log(rex.breed);              // Output: Beagle
console.log(rex instanceof Dog);     // Output: true
console.log(rex instanceof Animal);  // Output: true
console.log(rex instanceof Array);   // Output: false
  • In a child constructor, you must call super(...) before using this.
  • super.speak() calls the parent's version of the method, so you can extend it instead of rewriting it.
  • instanceof checks if an object was made from a class or any of its parents.

Common mistake: Forgetting new. Writing const a = BankAccount("Maya") throws "Class constructor cannot be invoked without 'new'".

When should you use classes vs plain objects and functions?

Classes are not always the best choice. Here is a simple guide:

Use thisWhen
Plain object { }You need one thing, or just data (settings, JSON from an API)
FunctionsYou transform data in and out with no saved state
ClassYou need many objects with the same shape and behavior, or private state

Good class examples are a Timer, a ShoppingCart, game characters or UI components. Keep inheritance shallow: one or two levels is usually enough. Deep chains like Animal > Mammal > Dog > SmallDog > Puppy get hard to change.

Practice exercise

Create a class Todo with title and a private #done field set to false. Add a method toggle(), a getter status that returns "Done" or "Open", and a static method fromJSON(obj) that creates a Todo from a plain object. Then create a PriorityTodo class that extends Todo and adds a priority property.

Hint: static fromJSON(obj) { return new Todo(obj.title); }.

Key takeaways

  • A class is a blueprint; new creates an instance and runs the constructor.
  • Getters and setters look like properties but run code.
  • static members belong to the class; #private fields are hidden from outside code.
  • extends inherits from a parent class, and super calls the parent.
  • Use classes for many similar objects with behavior; use plain objects and functions for simple data.