|

JavaScript 34 🧬 Class Method Override

Method overriding is when a subclass provides its own implementation of a method that already exists in its parent class. The subclass’s method “takes over” — when called on an instance of the subclass, the new method runs instead of the parent’s.


A Quick Look at the Example

class Animal {
    speak() {
        return "Some generic animal sound";
    }
}

class Dog extends Animal {
    // Override parent's speak()
    speak() {
        return "Woof!";
    }
}

class Cat extends Animal {
    // Override parent's speak()
    speak() {
        return "Meow!";
    }
}

const animal = new Animal();
const dog = new Dog();
const cat = new Cat();

console.log(animal.speak()); // "Some generic animal sound"
console.log(dog.speak());    // "Woof!" (overridden)
console.log(cat.speak());    // "Meow!" (overridden)

a. What is Method Overriding?

Method overriding allows a subclass to replace or customize a method inherited from its parent class.

Key Points

AspectDescription
DefinitionSubclass provides its own version of a parent’s method
Same nameThe overriding method has the same name as the parent’s
Same signatureSame parameters (ideally)
Runtime decisionWhich method runs is determined by the instance’s actual class

Why Override Methods?

ReasonDescription
SpecializationThe subclass needs different behavior
PolymorphismSame method name, different implementations
CustomizationAdapt parent behavior to specific needs
ExtensibilityAdd to parent behavior (via super.method())

b. Basic Method Overriding

class Shape {
    area() {
        return 0;
    }

    describe() {
        return `A shape with area ${this.area()}`;
    }
}

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

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

class Rectangle extends Shape {
    constructor(width, height) {
        super();
        this.width = width;
        this.height = height;
    }

    // Override area()
    area() {
        return this.width * this.height;
    }
}

const circle = new Circle(5);
const rect = new Rectangle(4, 6);

console.log(circle.describe()); // "A shape with area 78.53981633974483"
console.log(rect.describe());   // "A shape with area 24"

What’s happening:

  • Shape.describe() calls this.area()
  • this.area() resolves to the subclass’s area()polymorphism!

Visual:

circle.describe()
    │
    ▼
Shape.describe() {
    return `... ${this.area()}`;  ← this.area()
}
    │
    ▼
Circle.area() {                    ← Resolved to Circle's method!
    return Math.PI * this.radius ** 2;
}

c. Overriding vs Extending

There are two ways to override a method:

ApproachBehaviorUses super?
ReplaceCompletely replaces parent method❌ No
ExtendCalls parent + adds behavior✅ Yes

Replace (Complete Override)

class Animal {
    speak() {
        return "Animal sound";
    }
}

class Dog extends Animal {
    speak() {
        return "Woof!"; // Completely replaces parent
    }
}

console.log(new Dog().speak()); // "Woof!"

Extend (Call Parent + Add)

class Animal {
    speak() {
        return "Animal sound";
    }
}

class Dog extends Animal {
    speak() {
        const parentSound = super.speak(); // Call parent
        return `${parentSound} — but I'm a dog, so: Woof!`;
    }
}

console.log(new Dog().speak()); // "Animal sound — but I'm a dog, so: Woof!"

When to use each:

Use Replace WhenUse Extend When
Parent’s behavior is completely wrong for the subclassYou want to add to parent’s behavior
You need a totally different implementationYou want to reuse parent logic
Parent’s method doesn’t apply to the subclassYou want to customize, not replace

d. Calling Parent Method with super

The super.method() syntax lets you call the parent’s version of an overridden method.

class Vehicle {
    start() {
        return "Vehicle starting...";
    }
}

class Car extends Vehicle {
    start() {
        const base = super.start();
        return `${base} Car engine running.`;
    }
}

const car = new Car();
console.log(car.start());
// "Vehicle starting... Car engine running."

Rules for super.method():

  • Only available in methods of subclasses (classes with extends)
  • Always refers to the parent class’s method
  • Can be called from any method (not just constructors)
  • Works with any method, not just constructor

e. Real-World Examples

Example 1: Shape Hierarchy with Polymorphism

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

    area() { return 0; }
    perimeter() { return 0; }

    describe() {
        return `${this.name}: area=${this.area().toFixed(2)}, perimeter=${this.perimeter().toFixed(2)}`;
    }
}

class Circle extends Shape {
    constructor(radius) {
        super('Circle');
        this.radius = radius;
    }
    area() { return Math.PI * this.radius ** 2; }
    perimeter() { return 2 * Math.PI * this.radius; }
}

class Rectangle extends Shape {
    constructor(w, h) {
        super('Rectangle');
        this.w = w;
        this.h = h;
    }
    area() { return this.w * this.h; }
    perimeter() { return 2 * (this.w + this.h); }
}

class Square extends Rectangle {
    constructor(side) {
        super(side, side);
        this.name = 'Square'; // Override name property
    }
    // Overrides Rectangle.describe? No, inherits it
    // But area() and perimeter() from Rectangle work perfectly
}

const shapes = [
    new Circle(5),
    new Rectangle(4, 6),
    new Square(3)
];

shapes.forEach(s => console.log(s.describe()));
// Circle: area=78.54, perimeter=31.42
// Rectangle: area=24.00, perimeter=20.00
// Square: area=9.00, perimeter=12.00

Note: Square doesn’t override area() or perimeter() — it inherits from Rectangle because a square is a rectangle with equal sides.


Example 2: Employee Hierarchy

class Employee {
    constructor(name, salary) {
        this.name = name;
        this.salary = salary;
    }

    getRole() {
        return 'Employee';
    }

    getBonus() {
        return this.salary * 0.05; // 5% default
    }

    toString() {
        return `${this.name} — ${this.getRole()} — Bonus: $${this.getBonus().toFixed(2)}`;
    }
}

class Manager extends Employee {
    constructor(name, salary, department) {
        super(name, salary);
        this.department = department;
    }

    getRole() {
        return `Manager (${this.department})`;
    }

    // Extend parent's getBonus — managers get more
    getBonus() {
        return super.getBonus() * 2; // 2× the default bonus
    }
}

class Developer extends Employee {
    constructor(name, salary, language) {
        super(name, salary);
        this.language = language;
    }

    getRole() {
        return `Developer (${this.language})`;
    }

    getBonus() {
        return super.getBonus() * 1.5; // 1.5× the default
    }
}

const employees = [
    new Employee('Alice', 50000),
    new Manager('Bob', 90000, 'Engineering'),
    new Developer('Carol', 75000, 'JavaScript')
];

employees.forEach(e => console.log(e.toString()));
// Alice — Employee — Bonus: $2500.00
// Bob — Manager (Engineering) — Bonus: $9000.00 (2× of $4500)
// Carol — Developer (JavaScript) — Bonus: $5625.00 (1.5× of $3750)

Key insight: Manager.getBonus() calls super.getBonus()reusing the parent’s calculation, then modifying it.


Example 3: Custom Error Hierarchy

class AppError extends Error {
    constructor(message, code) {
        super(message);
        this.name = 'AppError';
        this.code = code;
        this.timestamp = new Date();
    }

    // Default toString
    toString() {
        return `[${this.name}] ${this.message} (code: ${this.code})`;
    }
}

class ValidationError extends AppError {
    constructor(field, value) {
        super(`Invalid value for ${field}: "${value}"`, 'VALIDATION');
        this.name = 'ValidationError';
        this.field = field;
        this.value = value;
    }

    // Extend parent's toString
    toString() {
        return super.toString() + ` — field: ${this.field}`;
    }
}

class NotFoundError extends AppError {
    constructor(resource) {
        super(`${resource} not found`, 'NOT_FOUND');
        this.name = 'NotFoundError';
        this.resource = resource;
    }

    // Fully override toString (no super)
    toString() {
        return `404 — ${this.resource} does not exist`;
    }
}

const err1 = new ValidationError('email', 'not-an-email');
const err2 = new NotFoundError('User');

console.log(err1.toString());
// "[ValidationError] Invalid value for email: "not-an-email" (code: VALIDATION) — field: email"

console.log(err2.toString());
// "404 — User does not exist"

console.log(err1 instanceof ValidationError); // true
console.log(err1 instanceof AppError);        // true
console.log(err1 instanceof Error);           // true

Example 4: Game Characters

class Character {
    constructor(name, hp) {
        this.name = name;
        this.hp = hp;
    }

    attack() {
        return `${this.name} attacks for 10 damage.`;
    }

    takeDamage(amount) {
        this.hp -= amount;
        return `${this.name} takes ${amount} damage. HP: ${this.hp}`;
    }

    describe() {
        return `${this.name} (HP: ${this.hp})`;
    }
}

class Warrior extends Character {
    constructor(name) {
        super(name, 150);
        this.rage = 0;
    }

    attack() {
        this.rage += 10;
        const base = super.attack();
        return `${base} Warrior rage: ${this.rage}`;
    }
}

class Mage extends Character {
    constructor(name) {
        super(name, 80);
        this.mana = 100;
    }

    attack() {
        if (this.mana >= 20) {
            this.mana -= 20;
            return `${this.name} casts a spell for 30 damage! (Mana: ${this.mana})`;
        }
        return `${this.name} is out of mana!`;
    }
}

class Healer extends Character {
    constructor(name) {
        super(name, 100);
    }

    attack() {
        return `${this.name} does 5 damage (weak attack).`;
    }

    heal(target) {
        target.hp += 20;
        return `${this.name} heals ${target.name} for 20 HP.`;
    }
}

const warrior = new Warrior('Conan');
const mage = new Mage('Merlin');
const healer = new Healer('Elara');

console.log(warrior.attack());
// "Conan attacks for 10 damage. Warrior rage: 10"
console.log(mage.attack());
// "Merlin casts a spell for 30 damage! (Mana: 80)"
console.log(healer.attack());
// "Elara does 5 damage (weak attack)."
console.log(healer.heal(warrior));
// "Elara heals Conan for 20 HP."

Complete Example

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Class Method Override</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background: #f8f9fa;
            color: #333;
            line-height: 1.6;
        }
        h1 { color: #007bff; border-bottom: 3px solid #007bff; padding-bottom: 10px; }
        h2 { color: #28a745; border-left: 4px solid #28a745; padding-left: 15px; margin-top: 30px; }
        .demo-box {
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            margin: 15px 0;
        }
        pre {
            background: #1e1e1e;
            color: #d4d4d4;
            padding: 15px;
            border-radius: 8px;
            overflow-x: auto;
            font-family: 'Courier New', monospace;
            font-size: 0.9rem;
            line-height: 1.8;
        }
        .keyword { color: #569cd6; }
        .string { color: #ce9178; }
        .number { color: #b5cea8; }
        .function { color: #dcdcaa; }
        .comment { color: #6a9955; }
        .boolean { color: #569cd6; }
        .class-name { color: #4ec9b0; }
        #output {
            background: #e9ecef;
            padding: 15px;
            border-radius: 8px;
            margin-top: 15px;
            min-height: 40px;
            font-family: 'Courier New', monospace;
            font-size: 0.85rem;
            border-left: 4px solid #007bff;
            white-space: pre-wrap;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin: 15px 0;
        }
        th, td {
            padding: 10px;
            border: 1px solid #ddd;
            text-align: left;
        }
        th { background: #007bff; color: white; }
        tr:nth-child(even) { background: #f8f9fa; }
        .btn {
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 6px;
            font-size: 1em;
            font-weight: bold;
            cursor: pointer;
            margin: 5px;
            transition: all 0.3s;
        }
        .btn:hover {
            background: #0056b3;
            transform: translateY(-2px);
        }
        .btn-success { background: #28a745; }
        .btn-success:hover { background: #1e7e34; }
        .hierarchy {
            font-family: 'Courier New', monospace;
            font-size: 0.85rem;
            background: #1e1e1e;
            color: #d4d4d4;
            padding: 20px;
            border-radius: 8px;
            margin: 15px 0;
            white-space: pre;
            overflow-x: auto;
            line-height: 1.8;
        }
        .class-display {
            font-family: 'Courier New', monospace;
            font-size: 0.95em;
            background: #f8f9fa;
            padding: 15px;
            border-radius: 8px;
            margin: 10px 0;
            border-left: 4px solid #007bff;
            white-space: pre-wrap;
            line-height: 1.8;
        }
        .log-display {
            background: #1e1e1e;
            color: #d4d4d4;
            padding: 15px;
            border-radius: 8px;
            font-family: 'Courier New', monospace;
            font-size: 0.85rem;
            max-height: 250px;
            overflow-y: auto;
            margin: 10px 0;
        }
        .log-entry { margin: 3px 0; }
        .log-success { color: #4ec9b0; }
        .log-info { color: #569cd6; }
        .log-warn { color: #dcdcaa; }
        .input-group {
            margin: 10px 0;
        }
        .input-group label {
            display: inline-block;
            min-width: 120px;
            font-weight: bold;
        }
        .input-group input {
            padding: 8px 12px;
            border: 2px solid #ddd;
            border-radius: 6px;
            font-size: 1em;
            width: 150px;
        }
        .input-group input:focus {
            outline: none;
            border-color: #007bff;
        }
    </style>
</head>
<body>

    <h1>Class Method Override</h1>

    <div class="demo-box">
        <h2>1. Basic Method Override</h2>
        <pre>
<span class="keyword">class</span> <span class="class-name">Animal</span> {
    <span class="function">speak</span>() {
        <span class="keyword">return</span> <span class="string">"Some generic animal sound"</span>;
    }
}

<span class="keyword">class</span> <span class="class-name">Dog</span> <span class="keyword">extends</span> <span class="class-name">Animal</span> {
    <span class="function">speak</span>() {
        <span class="keyword">return</span> <span class="string">"Woof!"</span>; <span class="comment">// Override</span>
    }
}

<span class="keyword">class</span> <span class="class-name">Cat</span> <span class="keyword">extends</span> <span class="class-name">Animal</span> {
    <span class="function">speak</span>() {
        <span class="keyword">return</span> <span class="string">"Meow!"</span>; <span class="comment">// Override</span>
    }
}
        </pre>
    </div>

    <div class="demo-box">
        <h2>2. Override vs Extend</h2>
        <div class="hierarchy">
<span style="color: #4ec9b0;">REPLACE (complete override)</span>
─────────────────────────────────
<span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">Dog</span> <span style="color: #569cd6;">extends</span> <span style="color: #4ec9b0;">Animal</span> {
    speak() {
        <span style="color: #569cd6;">return</span> <span style="color: #ce9178;">"Woof!"</span>;  <span style="color: #6a9955;">← Replaces parent entirely</span>
    }
}

<span style="color: #4ec9b0;">EXTEND (call parent + add)</span>
─────────────────────────────────
<span style="color: #569cd6;">class</span> <span style="color: #4ec9b0;">Dog</span> <span style="color: #569cd6;">extends</span> <span style="color: #4ec9b0;">Animal</span> {
    speak() {
        <span style="color: #569cd6;">const</span> parent = <span style="color: #569cd6;">super</span>.speak();  <span style="color: #6a9955;">← Calls parent</span>
        <span style="color: #569cd6;">return</span> parent + <span style="color: #ce9178;">" Woof!"</span>;  <span style="color: #6a9955;">← Adds new behavior</span>
    }
}
        </div>
    </div>

    <div class="demo-box">
        <h2>3. Method Resolution</h2>
        <pre>
<span class="keyword">const</span> dog = <span class="keyword">new</span> <span class="class-name">Dog</span>();

dog.<span class="function">speak</span>();
    │
    ├── Is speak on dog?       <span class="comment">No</span>
    ├── Is speak on Dog.proto? <span class="comment">Yes!</span> ← Uses this
    └── Is speak on Animal?    <span class="comment">(not needed)</span>
        </pre>
    </div>

    <div class="demo-box">
        <h2>4. Comparison Table</h2>
        <table>
            <tr>
                <th>Approach</th>
                <th>Behavior</th>
                <th>Uses super?</th>
                <th>When to Use</th>
            </tr>
            <tr>
                <td><strong>Override (Replace)</strong></td>
                <td>Replaces parent's method entirely</td>
                <td>❌ No</td>
                <td>Parent's logic doesn't apply</td>
            </tr>
            <tr>
                <td><strong>Extend (super)</strong></td>
                <td>Calls parent + adds behavior</td>
                <td>✅ Yes</td>
                <td>Customizing parent's behavior</td>
            </tr>
            <tr>
                <td><strong>Inherit</strong></td>
                <td>Uses parent's method as-is</td>
                <td>—</td>
                <td>Parent behavior is perfect</td>
            </tr>
        </table>
    </div>

    <div class="demo-box">
        <h2>5. Interactive: Shape Override Demo</h2>
        <div style="margin: 10px 0;">
            <button class="btn" onclick="showAllShapes()">Show All Shapes</button>
            <button class="btn btn-success" onclick="addCircle()">Add Circle</button>
            <button class="btn btn-success" onclick="addRectangle()">Add Rectangle</button>
            <button class="btn btn-success" onclick="addSquare()">Add Square</button>
            <button class="btn" onclick="compareOverrides()">Compare Overrides</button>
        </div>
        <div class="class-display" id="shapeDisplay">Click a button to explore method overriding</div>
    </div>

    <div class="demo-box">
        <h2>6. Interactive: Employee Bonus (Extend)</h2>
        <div style="margin: 10px 0;">
            <button class="btn" onclick="showEmployees()">Show All Employees</button>
            <button class="btn btn-success" onclick="addEmployee()">Add Employee</button>
            <button class="btn btn-success" onclick="addManager()">Add Manager</button>
            <button class="btn btn-success" onclick="addDeveloper()">Add Developer</button>
        </div>
        <div class="class-display" id="employeeDisplay">Click a button to see bonus calculations</div>
    </div>

    <div class="demo-box">
        <h2>7. Live Output — All Examples</h2>
        <div id="output">Loading...</div>
    </div>

    <script>
        // ============================================
        // Class Method Override — Live Demo
        // ============================================

        let results = [];

        // 1. Basic override
        results.push('📌 Basic Method Override:\n');

        class Animal {
            speak() {
                return "Some generic animal sound";
            }
        }

        class Dog extends Animal {
            speak() {
                return "Woof!";
            }
        }

        class Cat extends Animal {
            speak() {
                return "Meow!";
            }
        }

        const animal = new Animal();
        const dog = new Dog();
        const cat = new Cat();

        results.push('  new Animal().speak() → "' + animal.speak() + '"');
        results.push('  new Dog().speak()    → "' + dog.speak() + '"');
        results.push('  new Cat().speak()    → "' + cat.speak() + '"');
        results.push('');

        // 2. Override (replace)
        results.push('📌 Override (Replace Parent):\n');

        class Shape {
            area() {
                return 0;
            }
            describe() {
                return `Shape with area ${this.area().toFixed(2)}`;
            }
        }

        class Circle extends Shape {
            constructor(radius) {
                super();
                this.radius = radius;
            }
            area() {
                return Math.PI * this.radius ** 2;
            }
        }

        class Rectangle extends Shape {
            constructor(w, h) {
                super();
                this.w = w;
                this.h = h;
            }
            area() {
                return this.w * this.h;
            }
        }

        class Square extends Rectangle {
            constructor(side) {
                super(side, side);
                this.name = 'Square';
            }
            area() {
                return super.area(); // Calls Rectangle.area()
            }
            describe() {
                return `Square with side ${this.w}, area ${this.area().toFixed(2)}`;
            }
        }

        const circle = new Circle(5);
        const rect = new Rectangle(4, 6);
        const square = new Square(3);

        results.push('  circle.describe() → "' + circle.describe() + '"');
        results.push('  rect.describe()   → "' + rect.describe() + '"');
        results.push('  square.describe() → "' + square.describe() + '"');
        results.push('');

        // 3. Extend with super
        results.push('📌 Extend with super.method():\n');

        class Vehicle {
            start() {
                return "Vehicle starting...";
            }
        }

        class Car extends Vehicle {
            start() {
                const base = super.start();
                return `${base} Car engine running.`;
            }
        }

        const car = new Car();
        results.push('  new Vehicle().start() → "' + new Vehicle().start() + '"');
        results.push('  new Car().start()     → "' + car.start() + '"');
        results.push('  → Car calls super.start() then adds behavior');
        results.push('');

        // 4. Employee bonus — extend parent
        results.push('📌 Employee Bonus (Extending Parent):\n');

        class Employee {
            constructor(name, salary) {
                this.name = name;
                this.salary = salary;
            }
            getRole() {
                return 'Employee';
            }
            getBonus() {
                return this.salary * 0.05;
            }
            toString() {
                return `${this.name} (${this.getRole()}) — Bonus: $${this.getBonus().toFixed(2)}`;
            }
        }

        class Manager extends Employee {
            constructor(name, salary, department) {
                super(name, salary);
                this.department = department;
            }
            getRole() {
                return `Manager, ${this.department}`;
            }
            getBonus() {
                return super.getBonus() * 2;
            }
        }

        class Developer extends Employee {
            constructor(name, salary, language) {
                super(name, salary);
                this.language = language;
            }
            getRole() {
                return `Developer (${this.language})`;
            }
            getBonus() {
                return super.getBonus() * 1.5;
            }
        }

        const employees = [
            new Employee('Alice', 50000),
            new Manager('Bob', 90000, 'Engineering'),
            new Developer('Carol', 75000, 'JavaScript')
        ];

        employees.forEach(e => results.push('  ' + e.toString()));
        results.push('');

        // 5. Shape hierarchy with polymorphism
        results.push('📌 Polymorphism (Same Method, Different Behavior):\n');

        const shapes = [new Circle(5), new Rectangle(4, 6), new Square(3)];
        shapes.forEach(s => {
            results.push('  ' + s.constructor.name + '.area() → ' + s.area().toFixed(2));
        });
        results.push('');

        // 6. Game characters
        results.push('📌 Game Characters (Override):\n');

        class Character {
            constructor(name, hp) {
                this.name = name;
                this.hp = hp;
            }
            attack() {
                return `${this.name} attacks for 10 damage.`;
            }
            describe() {
                return `${this.name} (HP: ${this.hp})`;
            }
        }

        class Warrior extends Character {
            constructor(name) {
                super(name, 150);
                this.rage = 0;
            }
            attack() {
                this.rage += 10;
                const base = super.attack();
                return `${base} Rage: ${this.rage}`;
            }
        }

        class Mage extends Character {
            constructor(name) {
                super(name, 80);
                this.mana = 100;
            }
            attack() {
                if (this.mana >= 20) {
                    this.mana -= 20;
                    return `${this.name} casts a spell for 30 damage! (Mana: ${this.mana})`;
                }
                return `${this.name} is out of mana!`;
            }
        }

        const warrior = new Warrior('Conan');
        const mage = new Mage('Merlin');

        results.push('  ' + warrior.attack());
        results.push('  ' + warrior.attack());
        results.push('  ' + mage.attack());
        results.push('  ' + mage.attack());
        results.push('');

        // 7. Custom errors
        results.push('📌 Custom Errors (Override toString):\n');

        class AppError extends Error {
            constructor(message, code) {
                super(message);
                this.name = 'AppError';
                this.code = code;
            }
            toString() {
                return `[${this.name}] ${this.message} (code: ${this.code})`;
            }
        }

        class ValidationError extends AppError {
            constructor(field, value) {
                super(`Invalid value for ${field}: "${value}"`, 'VALIDATION');
                this.name = 'ValidationError';
                this.field = field;
            }
            toString() {
                return super.toString() + ` — field: ${this.field}`;
            }
        }

        class NotFoundError extends AppError {
            constructor(resource) {
                super(`${resource} not found`, 'NOT_FOUND');
                this.name = 'NotFoundError';
                this.resource = resource;
            }
            toString() {
                return `404 — ${this.resource} does not exist`;
            }
        }

        const err1 = new ValidationError('email', 'not-an-email');
        const err2 = new NotFoundError('User');

        results.push('  ' + err1.toString());
        results.push('  ' + err2.toString());
        results.push('');

        results.push('  Instance checks:');
        results.push('    err1 instanceof ValidationError → ' + (err1 instanceof ValidationError));
        results.push('    err1 instanceof AppError → ' + (err1 instanceof AppError));
        results.push('    err1 instanceof Error → ' + (err1 instanceof Error));
        results.push('    err2 instanceof NotFoundError → ' + (err2 instanceof NotFoundError));

        document.getElementById('output').textContent = results.join('\n');

        // ============================================
        // Interactive: Shape Override Demo
        // ============================================

        const interactiveShapes = [];

        function showAllShapes() {
            const display = document.getElementById('shapeDisplay');

            if (interactiveShapes.length === 0) {
                display.textContent = 'No shapes yet. Click "Add Circle", "Add Rectangle", or "Add Square".';
                return;
            }

            let text = 'All Shapes:\n';
            text += '─────────────────────\n';
            interactiveShapes.forEach((s, i) => {
                text += `${i + 1}. ${s.describe()}\n`;
            });
            display.textContent = text;
        }

        function addCircle() {
            const r = Math.floor(Math.random() * 8) + 2;
            interactiveShapes.push(new Circle(r));
            showAllShapes();
        }

        function addRectangle() {
            const w = Math.floor(Math.random() * 8) + 2;
            const h = Math.floor(Math.random() * 8) + 2;
            interactiveShapes.push(new Rectangle(w, h));
            showAllShapes();
        }

        function addSquare() {
            const side = Math.floor(Math.random() * 8) + 2;
            interactiveShapes.push(new Square(side));
            showAllShapes();
        }

        function compareOverrides() {
            const display = document.getElementById('shapeDisplay');
            display.textContent =
                `Method Override Comparison:\n` +
                `─────────────────────────────\n` +
                `\n` +
                `Shape.area()      → returns 0 (base)\n` +
                `Circle.area()     → OVERRIDES with πr²\n` +
                `Rectangle.area()  → OVERRIDES with w×h\n` +
                `Square.area()     → EXTENDS Rectangle (uses super)\n` +
                `\n` +
                `Circle.describe() → INHERITS from Shape\n` +
                `Square.describe() → OVERRIDES Shape.describe()\n` +
                `\n` +
                `→ Same method name, different implementations!`;
        }

        // ============================================
        // Interactive: Employee Bonus (Extend)
        // ============================================

        const interactiveEmployees = [
            new Employee('Alice', 50000),
            new Manager('Bob', 90000, 'Engineering'),
            new Developer('Carol', 75000, 'JavaScript')
        ];

        function showEmployees() {
            const display = document.getElementById('employeeDisplay');
            let text = 'All Employees:\n';
            text += '─────────────────────\n';
            interactiveEmployees.forEach((e, i) => {
                text += `${i + 1}. ${e.toString()}\n`;
            });
            display.textContent = text;
        }

        function addEmployee() {
            const n = Math.floor(Math.random() * 10000) + 40000;
            interactiveEmployees.push(new Employee('Employee', n));
            showEmployees();
        }

        function addManager() {
            const n = Math.floor(Math.random() * 20000) + 70000;
            const depts = ['Engineering', 'Sales', 'Marketing', 'HR'];
            const dept = depts[Math.floor(Math.random() * depts.length)];
            interactiveEmployees.push(new Manager('Manager', n, dept));
            showEmployees();
        }

        function addDeveloper() {
            const n = Math.floor(Math.random() * 15000) + 60000;
            const langs = ['JavaScript', 'Python', 'Rust', 'Go'];
            const lang = langs[Math.floor(Math.random() * langs.length)];
            interactiveEmployees.push(new Developer('Developer', n, lang));
            showEmployees();
        }

        // Initialize
        showEmployees();
    </script>

</body>
</html>

Quick Reference

Method Resolution Order

dog.speak()
    │
    ├── 1. Look on dog (own properties)     → not found
    ├── 2. Look on Dog.prototype             → found! ✅
    └── 3. (Not reached) Animal.prototype

Rule: JavaScript looks up the prototype chain from most specific to least specific — the first matching method wins.


Override vs Extend

ApproachSyntaxBehavior
Override (Replace)method() { ... }Replaces parent method
Extendmethod() { super.method(); ... }Calls parent + adds

When to Override

ScenarioApproach
Parent behavior doesn’t applyReplace
Need to customize parentExtend with super
Parent behavior is fineInherit (don’t override)
Add new behavior onlyAdd new method

super.method() Rules

RuleDescription
Only in subclassMust have extends
Refers to parentAlways the immediate parent
Any methodNot just constructors
Preserves thisthis still refers to the instance

Best Practices

Do This:

// Override when behavior is completely different
class Dog extends Animal {
    speak() {
        return "Woof!";
    }
}

// Use super to extend, not replace
class Manager extends Employee {
    getBonus() {
        return super.getBonus() * 2; // Reuse parent calculation
    }
}

// Keep overridden methods consistent in purpose
class Shape {
    area() { return 0; }
}
class Circle extends Shape {
    area() { return Math.PI * this.radius ** 2; } // Same purpose
}

// Call super.method() to preserve parent logic
class Logger extends BaseLogger {
    log(message) {
        super.log(message); // Parent's logic
        console.log(`[${new Date().toISOString()}] ${message}`); // Extra
    }
}

Don’t Do This:

// Don't override methods with different signatures
class Parent {
    greet(name) { }
}
class Child extends Parent {
    greet() { } // ❌ Different signature — confusing!
}

// Don't forget to call super when extending
class Child extends Parent {
    greet() {
        // Missing super.greet() — parent logic lost!
        return "Child";
    }
}

// Don't override methods to do unrelated things
class Shape {
    area() { return 0; }
}
class Circle extends Shape {
    area() { return "I am a circle"; } // ❌ Should return a number
}

// Don't call super in a class that doesn't extend
class MyClass {
    method() {
        super.method(); // ❌ SyntaxError
    }
}

Common Pitfalls

PitfallProblemSolution
Forgetting superParent logic lostCall super.method()
Calling super outside classSyntaxErrorOnly in subclass methods
Overriding with different signatureConfusing APIKeep consistent
Not overriding when neededWrong behaviorOverride when specialized
Overriding private methodsNot possibleUse regular methods

Real-World Example: Logger Hierarchy

class Logger {
    log(message) {
        console.log(`[LOG] ${message}`);
    }

    error(message) {
        console.error(`[ERROR] ${message}`);
    }
}

class TimestampLogger extends Logger {
    log(message) {
        const ts = new Date().toISOString();
        super.log(`${ts} — ${message}`);
    }

    error(message) {
        const ts = new Date().toISOString();
        super.error(`${ts} — ${message}`);
    }
}

class PrefixedLogger extends TimestampLogger {
    constructor(prefix) {
        super();
        this.prefix = prefix;
    }

    log(message) {
        super.log(`[${this.prefix}] ${message}`);
    }
}

const logger = new PrefixedLogger('API');
logger.log('Server started');
// "[LOG] 2024-01-15T10:30:00.000Z — [API] Server started"

Chain: Logger → TimestampLogger → PrefixedLogger — each adds a layer.


Pro Tip: Method overriding is the heart of polymorphism in OOP. Use it to:

  • Specialize behavior — subclasses do things differently
  • Extend behavior — call super.method() + add
  • Replace behavior — completely redefine

Key rules:

  • Same method name as parent
  • Use super.method() to call parent’s version
  • Keep method signatures consistent
  • Override for behavior changes, not signature changes

Remember: If the parent’s behavior is perfect, don’t override — just inherit! Override only when you need different or additional behavior. And use super.method() whenever you want to build on the parent’s logic instead of replacing it.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!