Back to JavaScript tutorials
Intermediate13 min read

Modern Classes

Use ES6 classes with fields, private members, static methods, and inheritance for structured application code.

Class Syntax and Constructors

Classes define a constructor with `constructor()` and methods on the prototype. Instance fields can be declared directly in the class body, initialized per instance without touching the constructor.

Method definitions are non-enumerable and use strict mode. Unlike object literal methods, class methods are not created anew per instance when placed on the prototype — memory efficient for many instances.

  • Instance fields in class body
  • Constructor for required setup
  • Methods shared via prototype
class User {
  id;
  name;
  constructor(id, name) {
    this.id = id;
    this.name = name;
  }
  greet() { return `Hi, ${this.name}`; }
}

Private Fields and Methods

Private fields and methods use `#` prefix and are accessible only within the class body. They provide true encapsulation without WeakMap workarounds, important for library internals and domain models exposing public APIs.

Private names are truly private — not accessible via bracket notation from outside. Subclasses cannot access parent private members, which enforces clear inheritance boundaries.

  • `#field` is truly private
  • Private methods for internal logic
  • Subclasses cannot access parent `#` members
class BankAccount {
  #balance = 0;
  deposit(amount) {
    if (amount > 0) this.#balance += amount;
  }
  get balance() { return this.#balance; }
}

Getters, Setters, and Static Members

Getters and setters define computed properties with validation logic. Static methods and fields belong to the constructor — use them for factory methods, constants, and utility functions scoped to the type.

Static blocks run once at class definition time, useful for parsing static configuration or registering subclasses. This replaces awkward immediately-invoked setup around class declarations.

  • Getters/setters for validated properties
  • Static methods for factories and utilities
  • Static blocks for one-time setup
class Config {
  static #instance;
  static getInstance() {
    return this.#instance ??= new Config();
  }
}

Inheritance with extends

`extends` creates a subclass linked to the parent prototype. `super()` must be called in the subclass constructor before using `this`. `super.method()` invokes parent implementations — essential when overriding behavior but reusing base logic.

Favor shallow inheritance hierarchies. Composition via injected dependencies often scales better than deep class trees, especially in testable service architectures.

  • Call `super()` before `this` in constructor
  • `super.method()` for parent behavior
  • Prefer composition over deep hierarchies
class Admin extends User {
  constructor(id, name, permissions) {
    super(id, name);
    this.permissions = permissions;
  }
}

Classes vs Factory Functions

Classes excel when instances share a clear type identity and methods, integrate with frameworks expecting constructors, and benefit from instanceof checks. Factory functions plus closures work better for simple objects, test doubles, and avoiding `new` binding issues.

In modern JavaScript, both patterns are valid. Choose based on team conventions and framework requirements rather than dogma — React class components are legacy, but domain models and SDK clients still commonly use classes.

  • Classes for typed entities and SDKs
  • Factories for lightweight objects
  • Match pattern to framework expectations

Get In Touch


Ready to discuss your next project? Drop me a message.