Skip to content

JavaScript Classes — ES6 Syntax, Inheritance, Static Methods, and Modern Patterns

DodaTech Updated 2026-06-29 5 min read

In this tutorial, you will learn about JavaScript Classes. We cover key concepts, practical examples, and best practices to help you master this topic.

JavaScript classes, introduced in ES6, provide syntactic sugar over the Prototype-based inheritance system. They don't introduce a new object model — they make it easier to work with prototypes and constructors. Modern JavaScript has expanded classes with private fields, static blocks, and ergonomic member declarations.

DodaTech uses classes for domain models, service objects, and plugin systems where inheritance hierarchies make sense.

What You'll Learn

  • Class syntax: constructor, methods, properties
  • Private and public fields
  • Getters and setters
  • Inheritance with extends and super
  • Static methods and properties
  • Static initialization blocks
  • Mixins and composition patterns

Basic Class Syntax

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

  getProfile() {
    return `${this.name} <${this.email}>`;
  }

  isNew() {
    const hoursOld = (Date.now() - this.createdAt.getTime()) / 3600000;
    return hoursOld < 24;
  }
}

const user = new User("Alice", "alice@example.com");
console.log(user.getProfile()); // "Alice <alice@example.com>"
console.log(user.isNew()); // true

Fields and Private Members

class BankAccount {
  // Public field (instance property)
  accountType = "checking";

  // Private field (ES2020+)
  #balance = 0;

  // Private field with default
  #accountNumber;

  constructor(owner, initialDeposit = 0) {
    this.owner = owner;
    this.#balance = initialDeposit;
    this.#accountNumber = crypto.randomUUID();
  }

  // Public method
  deposit(amount) {
    if (amount <= 0) throw new Error("Amount must be positive");
    this.#balance += amount;
    return this.#balance;
  }

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

  // Accessor for private field
  get balance() {
    return this.#balance;
  }

  // Private method
  #formatCurrency(amount) {
    return `$${amount.toFixed(2)}`;
  }

  getStatement() {
    return `${this.owner}: ${this.#formatCurrency(this.#balance)}`;
  }
}

const account = new BankAccount("Bob", 1000);
account.deposit(500);
console.log(account.balance); // 1500
console.log(account.getStatement()); // "Bob: $1500.00"
// account.#balance  // SyntaxError!

Getters and Setters

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

  get celsius() {
    return this._celsius;
  }

  set celsius(value) {
    if (value < -273.15) {
      throw new Error("Absolute zero is -273.15°C");
    }
    this._celsius = value;
  }

  get fahrenheit() {
    return this._celsius * 9/5 + 32;
  }

  set fahrenheit(value) {
    this._celsius = (value - 32) * 5/9;
  }

  get description() {
    if (this._celsius <= 0) return "freezing";
    if (this._celsius <= 15) return "cold";
    if (this._celsius <= 25) return "warm";
    if (this._celsius <= 35) return "hot";
    return "very hot";
  }
}

const temp = new Temperature(22);
console.log(temp.fahrenheit);  // 71.6
console.log(temp.description); // "warm"
temp.fahrenheit = 100;
console.log(temp.celsius);     // 37.78

Inheritance

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

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

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // Must call super before using this
    this.breed = breed;
  }

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

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

class Cat extends Animal {
  speak() {
    return `${this.name} meows.`;
  }
}

const dog = new Dog("Rex", "German Shepherd");
console.log(dog.speak());  // "Rex barks!"
console.log(dog.fetch());  // "Rex fetches the stick."
console.log(dog instanceof Dog);    // true
console.log(dog instanceof Animal); // true

// instanceof checks inheritance chain

Static Members

class MathUtils {
  static PI = 3.1415926535;

  static square(x) {
    return x * x;
  }

  static async fetchConfig(url) {
    const response = await fetch(url);
    return response.json();
  }

  // Static initialization block (ES2022+)
  static {
    console.log("MathUtils loaded");
    this.VERSION = "1.0.0";
  }
}

console.log(MathUtils.PI);         // 3.1415926535
console.log(MathUtils.square(5));  // 25
console.log(MathUtils.VERSION);    // "1.0.0"

Composition with Mixins

When you need to share behavior across unrelated classes:

// Mixin pattern
const TimestampMixin = (Base) => class extends Base {
  createdAt = new Date();
  updatedAt = new Date();

  touch() {
    this.updatedAt = new Date();
  }

  get age() {
    return Date.now() - this.createdAt.getTime();
  }
};

const SerializableMixin = (Base) => class extends Base {
  toJSON() {
    return Object.fromEntries(
      Object.entries(this).filter(
        ([key]) => !key.startsWith("#")  // Exclude private fields
      )
    );
  }
};

class Document {
  constructor(title) {
    this.title = title;
  }
}

class Post extends SerializableMixin(TimestampMixin(Document)) {
  constructor(title, content) {
    super(title);
    this.content = content;
  }
}

const post = new Post("Hello", "World");
console.log(post.createdAt); // Date object
console.log(post.toJSON());  // { title, content, createdAt, updatedAt }

When to Use Classes

// GOOD for: domain models with identity and behavior
class Customer {
  #orders = [];
  constructor(id, name) { this.id = id; this.name = name; }
  addOrder(order) { this.#orders.push(order); }
  get totalSpent() { ... }
}

// GOOD for: service objects with lifecycle
class DatabaseConnection {
  #pool;
  async connect() { ... }
  async query(sql) { ... }
  async close() { ... }
}

// AVOID for: stateless utility functions
// PREFER: plain functions and modules instead of single-method classes

// AVOID for: simple data containers
// PREFER: plain objects or TypeScript interfaces
const point = { x: 10, y: 20 };
// vs. class Point { constructor(x, y) { ... } }  // Overkill

Practice Questions

  1. Implement a Stack class (push, pop, peek, isEmpty, size) with a private #items array.

  2. Implement a Timer class with start/stop/reset and lap recording.

  3. Create a Vector2D class with add, subtract, dot product, and magnitude methods.

  4. Build a PluginManager class that registers and loads plugins.

  5. Create a Range class that behaves like a number range with start, end, step.

Challenge: Observable State Class

Build a class that implements the Observer Patternver" >}} pattern:

class Observable {
  // Private fields
  #state;
  #listeners = new Map();

  // ...
}
  • get(key) / set(key, value) with change detection
  • on(key, callback) / off(key, callback)
  • snapshot() returns immutable copy
  • undo() / redo() with history
  • subscribe(callback) for all changes

This mirrors the reactive state management DodaTech uses in its dashboard UI for real-time security scan status updates.

Real-World Task: Retryable API Client Class

Design a class ApiClient that wraps fetch with:

  • Base URL configuration
  • Request/response interceptors
  • Automatic retry with exponential backoff
  • Rate limit awareness (pause on 429)
  • Request timeout
  • Logging hooks

This is essentially the HTTP layer that DodaTech's service clients use to communicate with the core API.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro