11. Classes

Class Declaration

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    greet() {
        return `Hello, I'm ${this.name}`;
    }

    get isAdult() {
        return this.age >= 18;
    }

    set age(value) {
        if (value < 0) {
            throw new Error("Age cannot be negative");
        }
        this._age = value;
    }

    get age() {
        return this._age;
    }
}

const john = new Person("John", 30);
console.log(john.greet());    // "Hello, I'm John"
console.log(john.isAdult);    // true

Class Expression

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

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

const dog = new Animal("Dog");
console.log(dog.speak()); // "Dog makes a sound"

Inheritance

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

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

    eat() {
        return `${this.name} is eating`;
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name); // Call parent constructor
        this.breed = breed;
    }

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

    fetch() {
        return `${this.name} is fetching`;
    }
}

const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.speak());  // "Buddy barks"
console.log(dog.eat());    // "Buddy is eating" (inherited)
console.log(dog.fetch());  // "Buddy is fetching"

Static Methods and Properties

class MathUtils {
    static PI = 3.14159;

    static add(a, b) {
        return a + b;
    }

    static multiply(a, b) {
        return a * b;
    }
}

console.log(MathUtils.PI);         // 3.14159
console.log(MathUtils.add(5, 3));  // 8
console.log(MathUtils.multiply(4, 2)); // 8

// Static methods are not available on instances
const utils = new MathUtils();
// utils.add(1, 2); // Error

Private Fields and Methods (ES13+)

class BankAccount {
    #balance = 0; // Private field

    constructor(initialBalance) {
        this.#balance = initialBalance;
    }

    deposit(amount) {
        if (amount > 0) {
            this.#balance += amount;
        }
    }

    getBalance() {
        return this.#balance;
    }

    #calculateInterest() { // Private method
        return this.#balance * 0.05;
    }

    getInterest() {
        return this.#calculateInterest();
    }
}

const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
console.log(account.getInterest()); // 75

// console.log(account.#balance); // Error: Private field
// account.#calculateInterest(); // Error: Private method

Getters and Setters

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

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

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

    get kelvin() {
        return this.celsius + 273.15;
    }

    set kelvin(value) {
        this.celsius = value - 273.15;
    }
}

const temp = new Temperature(25);
console.log(temp.fahrenheit); // 77
console.log(temp.kelvin);     // 298.15

temp.fahrenheit = 86;
console.log(temp.celsius);    // 30

Mixins

const Flyable = {
    fly() {
        return `${this.name} is flying`;
    }
};

const Swimmable = {
    swim() {
        return `${this.name} is swimming`;
    }
};

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

Object.assign(Duck.prototype, Flyable, Swimmable);

const duck = new Duck("Donald");
console.log(duck.fly());  // "Donald is flying"
console.log(duck.swim()); // "Donald is swimming"

Abstract Classes (Convention)

class Shape {
    constructor() {
        if (this.constructor === Shape) {
            throw new Error("Abstract class cannot be instantiated");
        }
    }

    area() {
        throw new Error("Method 'area()' must be implemented");
    }

    perimeter() {
        throw new Error("Method 'perimeter()' must be implemented");
    }
}

class Circle extends Shape {
    constructor(radius) {
        super();
        this.radius = radius;
    }

    area() {
        return Math.PI * this.radius ** 2;
    }

    perimeter() {
        return 2 * Math.PI * this.radius;
    }
}

const circle = new Circle(5);
console.log(circle.area());      // 78.53981633974483
console.log(circle.perimeter()); // 31.41592653589793

// const shape = new Shape(); // Error: Abstract class cannot be instantiated

Class Checking

class Animal {}
class Dog extends Animal {}

const dog = new Dog();

console.log(dog instanceof Dog);     // true
console.log(dog instanceof Animal);  // true
console.log(dog.constructor === Dog); // true
console.log(Dog.prototype.isPrototypeOf(dog)); // true

Next Steps

Classes provide a cleaner syntax for object-oriented programming. Next, let's explore arrays and collections, which are essential data structures in ECMAScript.