Skip to content

Module Recap

Module 02 replaced plain object literals with a class-based Expense model. Here is what you covered and what you are taking into the rest of the course.

Classes are blueprints for creating objects — define the shape and behavior once, instantiate as many times as needed with new ClassName(...).

The constructor runs when an instance is created. Use it to assign properties via this, set defaults, validate input, and compute derived fields like createdAt.

Methods define the behavior of instances. They live on the prototype (shared, not copied) and use this to access the instance’s own data. Static methods belong to the class itself.

Inheritance with extends and super lets a subclass build on a parent class — gaining its properties and methods while adding its own. Use it for genuine “is-a” relationships.

Private fields (#field) protect internal state — they cannot be read or written from outside the class. Expose them selectively with getters.

Static members — both properties and methods — belong to the class rather than to instances. Use them for constants (CATEGORIES), factory methods (fromJSON), and validators (isValidCategory).

This is the Expense class you will use in BudgetBuddy:

class Expense {
static CATEGORIES = ['Food', 'Transport', 'Health', 'Entertainment', 'Other'];
#id;
#createdAt;
constructor({ id, description, amount, category, date, createdAt } = {}) {
if (!description || description.trim() === '') throw new Error('Description is required');
if (typeof amount !== 'number' || amount <= 0) throw new Error('Amount must be positive');
this.#id = id ?? crypto.randomUUID();
this.#createdAt = createdAt ?? Date.now();
this.description = description.trim();
this.amount = amount;
this.category = category ?? 'Other';
this.date = date ?? new Date().toISOString().split('T')[0];
}
get id() { return this.#id; }
get createdAt() { return this.#createdAt; }
format() {
return `${this.description}: $${this.amount.toFixed(2)}`;
}
isInCategory(category) {
return this.category.toLowerCase() === category.toLowerCase();
}
toJSON() {
return {
id: this.#id,
description: this.description,
amount: this.amount,
category: this.category,
date: this.date,
createdAt: this.#createdAt,
};
}
static fromJSON(data) {
return new Expense(data);
}
static isValidCategory(category) {
return Expense.CATEGORIES.includes(category);
}
}

The Expense instances you create are just objects — the array methods from Module 01 still work exactly the same way:

const expenses = [
new Expense({ description: 'Coffee', amount: 4.50, category: 'Food' }),
new Expense({ description: 'Bus pass', amount: 30.00, category: 'Transport' }),
new Expense({ description: 'Lunch', amount: 12.75, category: 'Food' }),
];
const foodExpenses = expenses.filter(e => e.isInCategory('Food'));
const total = expenses.reduce((acc, e) => acc + e.amount, 0);
const formatted = expenses.map(e => e.format());

The difference is that isInCategory is a method — the behavior lives with the data.

Module 03 introduces ES modules — splitting your code into multiple files with import and export. You will move Expense into its own file and import it wherever you need it, following the same organization pattern used in every modern JavaScript project.

ES Modules →