What you'll be able to do

By the end of this page you should be able to write a class, extend another with extends, call a parent constructor with super(), and explain that classes are syntax on top of prototypes — not a separate object system.

Previous post: prototype chain.

Who this is for

  • People who typed class App {} and wondered what new still does
  • Beginners who hit "Must call super constructor" and did not know why
  • Anyone who thinks class replaces the prototype story (it does not — it hides it politely)

You can skip this if you already extend classes and use super in methods confidently.

class is constructor + prototype, spelled neatly

class User {
  constructor(name) {
    this.name = name;
  }
  greet() {
    return "Hi, " + this.name;
  }
}

const u = new User("Asha");

Scenario A — compared to function User + User.prototype.greet. Same shape: new still required, methods still sit on the prototype.

Scenario B — reading modern tutorials. Almost all of them use class; knowing the prototype post means class is not magic dust.

extends and super in constructors

class Admin extends User {
  constructor(name, level) {
    super(name); // must run before using this in a derived class
    this.level = level;
  }
}

Scenario A — child needs parent setup. super(name) runs User’s constructor with this bound to the new Admin instance.

Scenario B — you forgot super. The engine throws before this.level = ... because the parent piece never ran.

The rule: in a derived class constructor, call super(...) before touching this.

super in methods — call parent behavior

class Admin extends User {
  greet() {
    return super.greet() + " (admin)";
  }
}

Scenario A — reuse parent logic. super.greet() calls User.prototype.greet with the same this.

Scenario B — override without copy-paste. You extend behavior instead of duplicating the parent's string format.

A tiny practice file

<!DOCTYPE html>
<html>
  <body>
    <button id="go">Make admin</button>
    <pre id="out"></pre>
    <script>
      class User {
        constructor(name) {
          this.name = name;
        }
        greet() {
          return "Hi, " + this.name;
        }
      }
      class Admin extends User {
        constructor(name, level) {
          super(name);
          this.level = level;
        }
        greet() {
          return super.greet() + " L" + this.level;
        }
      }
      document.getElementById("go").addEventListener("click", function () {
        const a = new Admin("Asha", 2);
        document.getElementById("out").textContent = a.greet();
      });
    </script>
  </body>
</html>

Click — you should see parent name setup plus child greeting text.

Mistakes I see a lot

1. Treating class as if new disappeared. User("Asha") is still wrong; use new User("Asha").

2. Using this before super() in a child constructor. Classic extends error message.

3. Arrow functions as class methods expecting super in instance methods. Instance methods are usually plain methods; arrows on the class body behave differently (often avoid arrows for prototype methods).

4. Deep extends trees for every tiny variation. Inheritance is one tool; the next post covers when composition fits better.

What to try before the next post

  1. Extend User with a Guest class that overrides greet but calls super.
  2. Log Object.getPrototypeOf(Admin.prototype) and relate it to User.prototype.
  3. Build the tiny HTML file.

Next in this series: composition vs inheritance — when not to grow another subclass.

Try this next outside the series

These mental models become more useful when you can point at a real framework problem they explain.