JavaScript Classes

Build objects with class syntax covering constructors, inheritance, static members, and private fields.

TL;DR

  1. Define object blueprints with class and constructor syntax.
  2. Inherit shared behavior using extends and super() calls.
  3. Hide internal state with private fields marked by #.

Class Basics

    class + constructor

    Define a blueprint for creating objects with shared methods.

    class User {
      constructor(name, email) {
        this.name = name;
        this.email = email;
      }
    }
    new keyword

    Create instances with new, which runs the constructor automatically.

    const user = new User('Ana', 'ana@example.com');
    console.log(user.name); // "Ana"
    Instance methods

    Define instance methods inside the class body without the function keyword.

    class User {
      constructor(name) { this.name = name; }
      greet() {
        return `Hi, ${this.name}`;
      }
    }
    Shared prototype

    Methods live on the prototype, so instances share a single function reference.

    const a = new User('Ana'), b = new User('Leo');
    console.log(a.greet === b.greet); // true

Static Methods and Properties

    static method

    Attach utility functions directly to the class instead of its instances.

    class MathHelper {
      static square(n) { return n * n; }
    }
    console.log(MathHelper.square(4)); // 16
    static field

    Hold class-level shared state across all created instances.

    class Counter {
      static count = 0;
      constructor() { Counter.count++; }
    }
    new Counter(); new Counter();
    console.log(Counter.count); // 2
    Factory method

    Return pre-configured instances using static factory functions.

    class User {
      static fromJSON(json) {
        const { name, email } = JSON.parse(json);
        return new User(name, email);
      }
    }

Getters, Setters, and Private Fields

    get

    Define an accessor property that computes its value on read.

    class Circle {
      constructor(radius) { this.radius = radius; }
      get area() { return Math.PI * this.radius ** 2; }
    }
    set

    Intercept assignments to validate or transform incoming values.

    class Circle {
      set radius(val) {
        if (val <= 0) throw new RangeError('Must be > 0');
        this._radius = val;
      }
    }
    Private fields (#)

    Prefix with # to encapsulate internal state from external access.

    class BankAccount {
      #balance = 0;
      deposit(n) { this.#balance += n; }
      get balance() { return this.#balance; }
    }
    Enforced privacy

    Accessing private fields outside the class throws a SyntaxError.

    const acc = new BankAccount();
    acc.#balance; // SyntaxError: Private field

Inheritance with Extends and Super

    extends

    Create a subclass that inherits methods and properties from a parent.

    class Animal {
      constructor(name) { this.name = name; }
      speak() { return `${this.name} speaks`; }
    }
    class Dog extends Animal {}
    super()

    Call super() in subclass constructors before accessing this.

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

    Extend parent behavior without rewriting inherited logic.

    class Dog extends Animal {
      speak() {
        return `${super.speak()} loudly`;
      }
    }
    instanceof

    Confirm an object belongs to a class or inherits from its prototype chain.

    const rex = new Dog('Rex', 'Lab');
    console.log(rex instanceof Animal); // true

Classes vs Prototypes

    Syntactic sugar

    Classes provide cleaner syntax over JavaScript's prototype inheritance.

    class Point {
      constructor(x, y) { this.x = x; this.y = y; }
    }
    Methods on prototype

    Class methods are placed directly onto the constructor's prototype.

    class Point {
      dist() { return Math.hypot(this.x, this.y); }
    }
    console.log(typeof Point.prototype.dist); // "function"
    typeof a class

    The typeof operator evaluates class declarations as functions.

    console.log(typeof Point); // "function"
    Requires new

    Invoking a class constructor without new throws a TypeError.

    class NewStyle {}
    NewStyle(); // TypeError: Must be invoked with 'new'

Tips

  1. Use private fields with a hash prefix to stop outside code from reading or changing internal state directly.
  2. Call super() before using this in a subclass constructor, since the parent must initialize the instance first.
  3. Prefer static methods for utility functions that relate to a class but don't need a specific instance.

Warnings

  1. Forgetting to call super() in a subclass constructor throws a ReferenceError before this can be accessed.
  2. Arrow function class fields capture this permanently, which can surprise developers expecting normal method binding rules.
  3. Class declarations are not hoisted like functions, so using a class before its definition throws an error.

In Practice

FAQ