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 whatnewstill does - Beginners who hit "Must call super constructor" and did not know why
- Anyone who thinks
classreplaces 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
- Extend
Userwith aGuestclass that overridesgreetbut callssuper. - Log
Object.getPrototypeOf(Admin.prototype)and relate it toUser.prototype. - 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.
- React Server Components explained — see execution boundaries and composition decisions in a modern app
- Node.js error handling — watch call stacks and thrown errors matter in real request code