JavaScript Classes

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

TL;DR

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

Tips

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

Warnings

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

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 every instance shares one copy instead of duplicating.

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

    Class declarations run in strict mode automatically, catching more silent bugs.

    class Demo {
      constructor() {
        undeclaredVar = 1; // throws ReferenceError in strict mode
      }
    }
    No hoisting

    Classes are not hoisted the way function declarations are, so define them before use.

    // new Greeter() here would throw a ReferenceError
    class Greeter {}

Static Methods and Properties

    static method

    Mark a method static to attach it to the class itself instead of each instance.

    class MathHelper {
      static square(n) {
        return n * n;
      }
    }
    MathHelper.square(4); // 16
    No instance this

    Static methods cannot access instance data through this because no instance exists.

    class Counter {
      static count = 0;
      constructor() {
        Counter.count++;
      }
    }
    Shared data

    Use static properties to track data shared across all instances, like a running total.

    new Counter();
    new Counter();
    console.log(Counter.count); // 2
    Factory methods

    Build factory methods as static functions that return configured instances.

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

    Static blocks let you run setup logic once when the class is first defined.

    class Config {
      static settings;
      static {
        Config.settings = loadDefaults();
      }
    }

Getters, Setters, and Private Fields

    get

    Define a property that computes its value each time it's read.

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

    Run logic, like validation, whenever a property is assigned.

    class Circle {
      set radius(value) {
        if (value <= 0) throw new RangeError('Radius must be positive');
        this._radius = value;
      }
    }
    Private fields (#)

    Mark fields private with a leading # so they can't be read or changed outside the class.

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

    Accessing a private field from outside the class throws a SyntaxError, not just undefined.

    const acc = new BankAccount();
    acc.#balance; // SyntaxError: Private field must be declared in an enclosing class
    Private methods

    Private methods work the same way, hiding internal logic from the public API.

    class Order {
      #calculateTax(amount) {
        return amount * 0.08;
      }
      getTotal(amount) {
        return amount + this.#calculateTax(amount);
      }
    }

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} makes a sound`;
      }
    }
    class Dog extends Animal {}
    super()

    Call super() inside a subclass constructor to run the parent constructor first.

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

    Override a parent method by redefining it with the same name in the subclass.

    class Dog extends Animal {
      speak() {
        return `${this.name} barks`;
      }
    }
    super.method()

    Call super.methodName() to reuse parent logic instead of duplicating it.

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

    Check whether an object inherits from a given class.

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

Classes vs Prototypes

    Syntactic sugar

    Classes are syntactic sugar over JavaScript's existing prototype-based inheritance model.

    class Point {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    }
    // Roughly equivalent to a constructor function + prototype assignment
    Methods on prototype

    A class method becomes a non-enumerable property on the constructor's prototype object.

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

    The typeof a class is still "function", confirming classes are functions under the hood.

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

    Unlike old-style constructor functions, classes throw an error if called without new.

    function OldStyle() {}
    OldStyle(); // works (but usually a bug)
    
    class NewStyle {}
    NewStyle(); // TypeError: Class constructor cannot be invoked without 'new'

In Practice

FAQ