JavaScript Classes — ES6 Syntax, Inheritance, Static Methods, and Modern Patterns
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
Implement a
Stackclass (push, pop, peek, isEmpty, size) with a private #items array.Implement a
Timerclass with start/stop/reset and lap recording.Create a
Vector2Dclass with add, subtract, dot product, and magnitude methods.Build a
PluginManagerclass that registers and loads plugins.Create a
Rangeclass 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 detectionon(key, callback)/off(key, callback)snapshot()returns immutable copyundo()/redo()with historysubscribe(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