JavaScript · Syntax · Beginner

OOP: Inheritance and Method Overriding

9 tasks

OOP problems in JavaScript on inheritance, super(), constructor, method overriding, toString(), custom class methods, and working with objects.

Inheritance — extends and super

#
Inheritance lets one class reuse all the code from another and then extend or override specific parts. In JavaScript this is done with extends: ```javascript class Animal { constructor(name) { this.name = name; } } class Dog extends Animal { bark() { return `${this.name} says woof`; } } const d = new Dog("Rex"); console.log(d.name); // Rex — inherited from Animal console.log(d.bark()); // Rex says woof ``` Dog inherits constructor from Animal, so new Dog("Rex") works without Dog defining its own constructor. Dog also gets any other methods Animal has. When the child class needs its own constructor, it must call super() first — before any reference to this: ```javascript class Admin extends User { constructor(name, role) { super(name); // calls User's constructor this.role = role; } } ``` super() is not optional here — forgetting it causes a ReferenceError when you try to use this. The rule is: if you write a constructor in a subclass, the first thing in it must be super(). Overriding a method means defining a method with the same name in the child class. JavaScript always calls the most specific version — the child's method takes priority.

toString() and custom methods

#
JavaScript classes can override built-in methods to control how objects behave in standard operations. toString() controls what JavaScript returns when an object is coerced to a string — in template literals, string concatenation, or explicit String() calls: ```javascript class Book { constructor(title, pages) { this.title = title; this.pages = pages; } toString() { return `Book: ${this.title}, ${this.pages} pages`; } } const b = new Book("JavaScript Basics", 300); console.log(String(b)); // Book: JavaScript Basics, 300 pages console.log(`${b}`); // Book: JavaScript Basics, 300 pages ``` Without toString(), `${b}` would produce [object Object] — the default representation, which is rarely useful. JavaScript does not have a built-in equivalent of Python's __len__. To expose a length-like concept, define a regular method: ```javascript class Box { constructor(items) { this.items = items; } size() { return this.items.length; } } ``` Or use a getter, which looks like a property but calls a method: ```javascript class Box { constructor(items) { this.items = items; } get length() { return this.items.length; } } const b = new Box([1, 2, 3]); console.log(b.length); // 3 — called as a property, not b.length() ``` Getters let the outside world read computed data without knowing it is a method call.

Inheritance and method patterns

#
Inheriting the parent's constructor and adding new behaviour — call super() first, then set the new properties: ```javascript class Playlist { constructor() { this.songs = []; } add(song) { this.songs.push(song); } remove(song) { const index = this.songs.indexOf(song); if (index !== -1) { this.songs.splice(index, 1); } } } class CustomPlaylist extends Playlist { constructor(name) { super(); // sets up this.songs = [] this.name = name; // adds the new property } } ``` super() must be called when the parent's constructor sets up state the child depends on. Skipping it means this.songs is never created. A method that overrides the parent and adds new behaviour: ```javascript class MagicBox extends Box { randomItem() { const i = Math.floor(Math.random() * this.items.length); return this.items[i]; } } ``` MagicBox gets everything Box has. Math.random() returns a float in [0, 1), so Math.floor(Math.random() * n) gives a random integer in [0, n-1]. A property with a default value set in the constructor: ```javascript class Message { constructor(text) { this.text = text; this.spam = false; } markSpam() { this.spam = true; } } ``` Properties do not have to come from parameters. spam starts as false for every new Message — that is the correct default.
01

Animal and Dog through inheritance.

#

Create an Animal class, and the objects created from it must have a name property. The value for this property is passed when creating the object through the constructor. That is, the object describes an animal (any animal) and stores its name. Then create a Dog class that inherits from Animal. In the Dog class add a toString method that returns a string containing the type of animal (haha, sorry, "animal type" sounds funny) and its name: "Dog: Rex" # example of the returned string The idea is this: the base class Animal stores the main "behavior" of animal objects, while the class Dog adds details for a specific type of animal.

class Animal {
    // your code here
}
class Dog extends Animal {
    // your code here
}
Solution
class Animal {
    constructor(name) {
        this.name = name;
    }
}
class Dog extends Animal {
    toString() {
        return `Dog: ${this.name}`;
    }
}
02

Book with string representation.

#

Create a Book class with title and pages object properties. Add a toString() method that returns: "Book: JavaScript Basics (120 pages)" You can also add a length() method that returns the number of pages in the book.

class Book {
    // your code here
}
Solution
class Book {
    constructor(title, pages) {
        this.title = title;
        this.pages = pages;
    }
    length() {
        return this.pages;
    }
    toString() {
        return `Book: ${this.title} (${this.pages} pages)`;
    }
}
03

User and Admin.

#

Let’s do something similar to the 1st one again: Task: Create a User class with a name property. Create an Admin class that inherits from User. Override the toString method so the output is: "Admin: Alex" And for a regular User the toString output should simply be: "Alex"

class User {
    // your code here
}
class Admin extends User {
    // your code here
}
Solution
class User {
    constructor(name) {
        this.name = name;
    }
    toString() {
        return this.name;
    }
}
class Admin extends User {
    toString() {
        return `Admin: ${this.name}`;
    }
}
04

Box with items.

#

Create a Box class with an items_count property. Add a length() method that returns the number of items. Also add add and remove methods that can put objects into and take objects out of the box. This can be done simply with the items_count counter (just count without extra complexity), or you can make an array of items in the box (strings) and adapt the class to work with the array (array as an object property).

class Box {
    // your code here
}
Solution
class Box {
    constructor(itemsCount) {
        this.itemsCount = itemsCount;
    }

    add(count) {
        if (count > 0) {
            this.itemsCount += count;
        }
    }

    remove(count) {
        if (count > 0) {
            this.itemsCount -= count;

            if (this.itemsCount < 0) {
                this.itemsCount = 0;
            }
        }
    }

    getCount() {
        return this.itemsCount;
    }
}
05

Message with spam flag.

#

Create a Message class with text and spam properties. The text value is passed when creating the object through the constructor, and spam is initially equal to null. Add methods: isSpam() — checks whether the text contains words похожие на spam, returns true/false and stores the result in this.spam; length() — returns the length of the text.

class Message {
    // your code here
}
Solution
class Message {
    constructor(text) {
        this.text = text;
        this.spam = null;
        this.spamTriggers = ["buy", "expensive"];
    }

    isSpam() {
        if (this.spam !== null) {
            return this.spam;
        }

        for (let trigger of this.spamTriggers) {
            if (this.text.toLowerCase().includes(trigger)) {
                this.spam = true;
                return true;
            }
        }

        this.spam = false;
        return false;
    }

    length() {
        return this.text.length;
    }
}

let m = new Message("Buying old TVs for a high price!!!");
console.log(m.isSpam());
console.log(m.length());
06

Employee and Manager.

#

Create an Employee class with name and salary properties. Make a method in Employee that can change the salary. Create a Manager class that inherits from Employee. Override the toString method so that the output is: Manager John earns 5000 Make sure the salary changing method also works for Manager

class Employee {
    // your code here
}
class Manager extends Employee {
    // your code here
}
Solution
class Employee {
    constructor(name, salary) {
        this.name = name;
        this.salary = salary;
    }

    changeSalary(coef) {
        this.salary = Math.round(this.salary * coef);
    }
}

class Manager extends Employee {
    toString() {
        return `Manager ${this.name} earns ${this.salary}`;
    }
}

let manager = new Manager("Bob", 1000);

manager.changeSalary(1.1);

console.log(manager.toString());
07

Playlist and CustomPlaylist.

#

Create a Playlist class with a songs property (array). Add add and remove methods for adding and removing songs. Add a reverse() method that reverses the track array. Add a length() method that returns the number of songs. Add an isEmpty() method that checks whether the playlist is empty or not. Then create a child class CustomPlaylist, where the add() method allows duplicate songs to be added to the playlist. In the base Playlist duplicates must not be allowed.

class Playlist {
    // your code here
}
Solution
class Playlist {
    constructor(songs) {
        this.songs = songs;
    }

    add(song) {
        if (!this.songs.includes(song)) {
            this.songs.push(song);
        }
    }

    remove(song) {
        if (this.songs.includes(song)) {
            this.songs.splice(this.songs.indexOf(song), 1);
        }
    }

    reverse() {
        this.songs.reverse();
    }

    length() {
        return this.songs.length;
    }

    isEmpty() {
        return this.songs.length === 0;
    }
}

class CustomPlaylist extends Playlist {
    add(song) {
        this.songs.push(song);
    }
}
08

Named CustomPlaylist.

#

Improve the previous CustomPlaylist class by adding its own constructor. It must call the parent class constructor through super(songs), and then save the playlist name into the name property.

class CustomPlaylist extends Playlist {
    constructor(songs, name) {
        super(songs);
        // your code here
    }

    add(song) {
        this.songs.push(song);
    }
}
Solution
class Playlist {
    constructor(songs) {
        this.songs = songs;
    }

    add(song) {
        if (!this.songs.includes(song)) {
            this.songs.push(song);
        }
    }
}

class CustomPlaylist extends Playlist {
    constructor(songs, name) {
        super(songs);
        this.name = name;
    }

    add(song) {
        this.songs.push(song);
    }

    toString() {
        return `Playlist: ${this.name}`;
    }
}
09

MagicBox with a random item.

#

Let’s return to the Box class (the code is below). Let’s inherit from it and create a MagicBox class. The only added behavior is that when a box object is created, a random item from an array will appear inside it. Define the array of possible random items wherever you want, it does not matter, even in the global scope. Template:

class Box {
    constructor() {
        this.items = [];
        this.itemsCount = this.items.length;
    }
    add(item) {
        this.items.push(item);
        this.itemsCount = this.items.length;
    }
    remove(item) {
        if (this.items.includes ? this.items.includes(item) : (item in this.items)) {
            this.items.splice(this.items.indexOf(item), 1);
            this.itemsCount = this.items.length;
        } else {
            console.log("The cat from the box says: That's not here, goodbye!");
        }
    }
    length() {
        return this.itemsCount;
    }
}
let things = ["Hat", "Umbrella", "Mug"];
class MagicBox extends Box {
    constructor() {
        // call super() and add a random item
    }
}
Solution
function choice(items) {
    return items[Math.floor(Math.random() * items.length)];
}

class Box {
    constructor() {
        this.items = [];
        this.itemsCount = this.items.length;
    }

    add(item) {
        this.items.push(item);
        this.itemsCount = this.items.length;
    }

    remove(item) {
        if (this.items.includes(item)) {
            this.items.splice(this.items.indexOf(item), 1);
            this.itemsCount = this.items.length;
        } else {
            console.log("The cat from the box says: That's not here, goodbye!");
        }
    }

    length() {
        return this.itemsCount;
    }
}

const things = ["Hat", "Umbrella", "Mug"];

class MagicBox extends Box {
    constructor() {
        super();
        this.items.push(choice(things));
        this.itemsCount = this.items.length;
    }
}