JavaScript Classes

Classes are a way to create objects that share the same structure and behaviour. They make it easier to create multiple objects of the same type without repeating yourself.

Under the hood, JavaScript classes are syntactic sugar over the prototype system. They do not introduce a new object model; they just make the syntax cleaner and more familiar if you have used other languages. See Prototypes for the underlying mechanics.

Defining a class

Use the class keyword followed by a PascalCase name.

class Person {
  // code goes here
}

Constructor

The constructor is a special method that runs when you create a new instance with new. Use it to set up the initial state of the object.

class Person {
  constructor(firstName, lastName, birthYear) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.birthYear = birthYear;
  }
}

Each class can only have one constructor. Writing two will throw a SyntaxError.

Creating instances

Use new to create an object from a class.

const p1 = new Person('Anna', 'Wood', 1995);
const p2 = new Person('Joe', 'Hopkins', 1990);

console.log(p1.firstName); // Anna
console.log(p2.birthYear); // 1990

Methods

Methods are functions defined inside the class body. They are available on every instance.

class Person {
  constructor(firstName, lastName, birthYear) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.birthYear = birthYear;
  }

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  getAge() {
    return new Date().getFullYear() - this.birthYear;
  }
}

const p1 = new Person('Anna', 'Wood', 1995);
console.log(p1.getFullName()); // Anna Wood
console.log(p1.getAge());      // 30 (in 2025)

Getters and setters

Getters and setters let you access computed values like properties and add validation when setting values.

class Temperature {
  constructor(celsius) {
    this._celsius = celsius;
  }

  get fahrenheit() {
    return this._celsius * 1.8 + 32;
  }

  get celsius() {
    return this._celsius;
  }

  set celsius(value) {
    if (value < -273.15) {
      throw new Error('Temperature below absolute zero');
    }
    this._celsius = value;
  }
}

const temp = new Temperature(100);
console.log(temp.fahrenheit); // 212
console.log(temp.celsius);    // 100

temp.celsius = 0;
console.log(temp.fahrenheit); // 32

You access a getter with dot notation (no parentheses), like a regular property.

Private class fields

Private fields (ES2022) start with # and can only be accessed inside the class. This is true privacy, not just a naming convention.

class BankAccount {
  #balance = 0; // private field

  deposit(amount) {
    if (amount <= 0) throw new Error('Amount must be positive');
    this.#balance += amount;
  }

  withdraw(amount) {
    if (amount > this.#balance) throw new Error('Insufficient funds');
    this.#balance -= amount;
  }

  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount();
account.deposit(100);
account.withdraw(30);
console.log(account.getBalance()); // 70

// This throws a SyntaxError: you cannot access private fields from outside
console.log(account.#balance);

Static methods

Static methods belong to the class itself, not to any instance. Call them on the class directly.

class MathHelper {
  static square(n) {
    return n * n;
  }

  static cube(n) {
    return n * n * n;
  }

  static generateId() {
    return Math.floor(Math.random() * 10000);
  }
}

console.log(MathHelper.square(4)); // 16
console.log(MathHelper.cube(3));   // 27
console.log(MathHelper.generateId()); // random number

Static methods are useful for utility functions that are logically related to the class but do not need an instance.

Inheritance

A subclass extends a parent class with the extends keyword. It inherits all the parent’s methods. Call super() in the subclass constructor to run the parent constructor first.

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

class Student extends Person {
  constructor(firstName, lastName, school) {
    super(firstName, lastName); // must call super() first
    this.school = school;
  }

  getInfo() {
    return `${this.getFullName()} attends ${this.school}`;
  }
}

const s1 = new Student('John', 'Smith', 'MIT');
console.log(s1.getInfo());     // John Smith attends MIT
console.log(s1.getFullName()); // John Smith (inherited from Person)

Common mistakes

Forgetting super() in a subclass constructor

If you write a constructor in a subclass, you must call super() before you access this. If you forget, you get a ReferenceError.

Using this in a static method

Static methods do not have access to instance properties. Inside a static method, this refers to the class itself, not an instance.

class Counter {
  count = 0; // instance field

  static reset() {
    this.count = 0; // wrong: this is the Counter class, not an instance
  }
}

Expecting private field access from outside the class

Private fields with # throw a SyntaxError if you try to access them outside the class. This is intentional and the whole point of using them.