JavaScript Fundamentals: Classes in JavaScript

· 12 min read · beginner
classes oop beginner javascript-fundamentals
Part 5 of the javascript-fundamentals series

Classes are a fundamental building block of object-oriented programming in JavaScript. Introduced in ES6, they provide a cleaner syntax for creating objects and implementing inheritance. This tutorial covers everything you need to know about classes in modern JavaScript.

What Are Classes?

At their core, classes are blueprints for creating objects. They define properties and methods that all instances of the class will have. Think of a class as a recipe — it tells JavaScript how to create something, but you need to actually make the dish (create an instance) to use it.

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

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

const alice = new Person("Alice", 30);
console.log(alice.greet());
// Hello, my name is Alice

Class Declaration and Expression

You can declare a class using the class keyword, similar to function declarations. There’s also a class expression form, which is less common but useful in certain situations.

// Class declaration
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  area() {
    return this.width * this.height;
  }
}

// Class expression
const Square = class {
  constructor(side) {
    this.side = side;
  }

  area() {
    return this.side * this.side;
  }
};

const rect = new Rectangle(10, 5);
console.log(rect.area()); // 50

const sq = new Square(7);
console.log(sq.area());   // 49

Constructor

The constructor is a special method called when you create a new instance with new. It’s where you initialize the object’s properties. A class can only have one constructor.

class BankAccount {
  constructor(balance = 0) {
    this.balance = balance;
    this.transactions = [];
  }

  deposit(amount) {
    if (amount <= 0) {
      throw new Error("Deposit amount must be positive");
    }
    this.balance += amount;
    this.transactions.push({ type: "deposit", amount });
    return this.balance;
  }

  withdraw(amount) {
    if (amount > this.balance) {
      throw new Error("Insufficient funds");
    }
    this.balance -= amount;
    this.transactions.push({ type: "withdraw", amount });
    return this.balance;
  }
}

const account = new BankAccount(100);
account.deposit(50);
account.withdraw(30);
console.log(account.balance); // 120

Methods

Methods are functions defined inside a class. They can access and modify the object’s properties via this. JavaScript classes support several types of methods.

Instance Methods

These are the most common — they’re available on every instance of the class.

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

  toFahrenheit() {
    return (this.celsius * 9/5) + 32;
  }

  toKelvin() {
    return this.celsius + 273.15;
  }
}

const temp = new Temperature(25);
console.log(temp.toFahrenheit()); // 77
console.log(temp.toKelvin());     // 298.15

Getters and Setters

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

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

  get name() {
    return this._name;
  }

  set name(value) {
    if (typeof value !== "string" || value.length < 2) {
      throw new Error("Name must be a string with at least 2 characters");
    }
    this._name = value;
  }

  get isLocked() {
    return this._loginAttempts >= 3;
  }

  recordFailedLogin() {
    this._loginAttempts++;
  }
}

const user = new User("Bob");
console.log(user.name);     // Bob

user.name = "Alice";
console.log(user.name);     // Alice

user.recordFailedLogin();
user.recordFailedLogin();
user.recordFailedLogin();
console.log(user.isLocked); // true

Static Methods

Static methods belong to the class itself, not to instances. They’re useful for utility functions related to the class.

class MathUtils {
  static average(...numbers) {
    if (numbers.length === 0) return 0;
    return numbers.reduce((a, b) => a + b, 0) / numbers.length;
  }

  static clamp(value, min, max) {
    return Math.min(Math.max(value, min), max);
  }
}

console.log(MathUtils.average(1, 2, 3, 4, 5)); // 3
console.log(MathUtils.clamp(15, 0, 10));       // 10
console.log(MathUtils.clamp(-5, 0, 10));       // 0

Static Properties

Static properties (added in ES2022) work similarly to static methods — they belong to the class, not instances.

class Counter {
  static count = 0;

  constructor() {
    Counter.count++;
  }

  static reset() {
    Counter.count = 0;
  }
}

new Counter();
new Counter();
new Counter();
console.log(Counter.count); // 3

Counter.reset();
console.log(Counter.count); // 0

Inheritance with extends

Inheritance lets you create a new class based on an existing one. The child class inherits all properties and methods from the parent and can add new ones or override existing behavior.

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

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

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

  speak() {
    return `${this.name} barks`;
  }

  fetch() {
    return `${this.name} fetches the ball`;
  }
}

const dog = new Dog("Rex", "German Shepherd");
console.log(dog.speak());  // Rex barks
console.log(dog.fetch());  // Rex fetches the ball
console.log(dog.breed);    // German Shepherd

The super keyword calls the parent class’s constructor or methods. It must be called before accessing this in the constructor.

Private Fields

Private fields (prefixed with #) restrict access to class internals. They can’t be accessed or modified from outside the class, enforcing encapsulation.

  #balance;
  #transactions;

  constructor(initialBalance = 0) {
    this.#balance = initialBalance;
    this.#transactions = [];
  }

  deposit(amount) {
    if (amount <= 0) return false;
    this.#balance += amount;
    this.#transactions.push({ type: "deposit", amount });
    return true;
  }

  getBalance() {
    return this.#balance;
  }

  getTransactions() {
    return [...this.#transactions];
  }
}

const account = new BankAccount(100);
account.deposit(50);

console.log(account.getBalance());     // 150
console.log(account.getTransactions());
// [{ type: "deposit", amount: 50 }]

// These would throw errors:
// console.log(account.#balance);
// account.#balance = 1000000;

Class Expression and instanceof

The instanceof operator checks if an object belongs to a class (or its parent classes).

class Vehicle {
  constructor(type) {
    this.type = type;
  }
}

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

const myCar = new Car("Toyota");

console.log(myCar instanceof Car);     // true
console.log(myCar instanceof Vehicle); // true
console.log(myCar instanceof Object);   // true

Quick Reference

Here’s a summary of class features:

FeatureSyntaxDescription
Declarationclass Name { }Create a class
Constructorconstructor(props) { }Initialize instances
MethodmethodName() { }Instance method
Getterget prop() { }Computed property
Setterset prop(val) { }Validated assignment
Staticstatic method() { }Class-level method
Private#fieldEncapsulated field
Inheritanceclass Child extends ParentExtend a class
Parent callsuper.method()Call parent method

Summary

Classes in JavaScript provide a clean, object-oriented way to structure your code. Key takeaways:

  • Use class keyword to declare classes, constructor for initialization
  • Methods, getters, setters, and static methods add behavior
  • Private fields (#) enforce encapsulation
  • extends creates inheritance, super accesses parent class
  • Classes are just syntactic sugar over JavaScript’s prototype system

Understanding classes is essential for working with many JavaScript frameworks and writing maintainable, reusable code.