Inheritance & Private Fields
Inheritance & Private Fields
Inheritance lets one class build on another, reusing and extending behavior. Private class fields, standardized in ES2022, provide true encapsulation within JavaScript classes.
extends
The extends keyword creates a subclass (child class) from a parent class. The subclass inherits all methods from its parent's prototype chain.
class Dog extends Animal {
speak() {
return `${this.name} barks.`;
}
}super
In a subclass constructor, you must call super(...args) before accessing this. This calls the parent's constructor to initialize the inherited portion of the object.
super.method() calls the parent class's version of a method, which is useful when overriding.
class GuideDog extends Dog {
constructor(name, owner) {
super(name); // must come first
this.owner = owner;
}
speak() {
return super.speak() + ` Guide for ${this.owner}.`;
}
}Method Overriding
A subclass can redefine a method from its parent. The new definition takes precedence for instances of the subclass.
Private Fields (#)
A field or method prefixed with # is private to the class body. It cannot be accessed or modified from outside the class — not even by subclasses.
class Safe {
#pin;
constructor(pin) { this.#pin = pin; }
unlock(attempt) { return attempt === this.#pin; }
}Trying to access safe.#pin outside the class causes a SyntaxError at parse time, not a runtime error.
instanceof
obj instanceof Class traverses the prototype chain and returns true if Class.prototype appears anywhere in it.
const d = new GuideDog("Rex", "Alice");
d instanceof GuideDog // true
d instanceof Dog // true
d instanceof Animal // trueMixin Pattern
JavaScript only supports single inheritance, but you can simulate mixins using functions that extend a base class:
const Serializable = (Base) => class extends Base {
serialize() { return JSON.stringify(this); }
};Code Examples
class Shape {
constructor(color = "black") {
this.color = color;
}
area() { return 0; }
toString() {
return `${this.constructor.name} [color=${this.color}, area=${this.area().toFixed(2)}]`;
}
}
class Rectangle extends Shape {
constructor(width, height, color) {
super(color);
this.width = width;
this.height = height;
}
area() { return this.width * this.height; }
}
class Circle extends Shape {
constructor(radius, color) {
super(color);
this.radius = radius;
}
area() { return Math.PI * this.radius ** 2; }
}
const shapes = [
new Rectangle(5, 3, "red"),
new Circle(4, "blue"),
new Rectangle(2, 8, "green"),
];
shapes.forEach((s) => console.log(s.toString()));
console.log(shapes[1] instanceof Shape);super(color) calls the Shape constructor to initialize the color property. Each subclass overrides area() with its own implementation. Polymorphism lets us call .toString() uniformly across all shape types.
class BankAccount {
#balance;
#transactionLog = [];
constructor(owner, initialBalance) {
this.owner = owner;
this.#balance = initialBalance;
}
deposit(amount) {
this.#balance += amount;
this.#transactionLog.push(`+${amount}`);
}
withdraw(amount) {
if (amount > this.#balance) throw new Error("Insufficient funds");
this.#balance -= amount;
this.#transactionLog.push(`-${amount}`);
}
get balance() { return this.#balance; }
get history() { return [...this.#transactionLog]; }
}
const acct = new BankAccount("Alice", 500);
acct.deposit(200);
acct.withdraw(100);
console.log("Balance:", acct.balance);
console.log("History:", acct.history);
// Cannot access private fields from outside
console.log(typeof acct["#balance"]); // undefined — not accessiblePrivate fields (#balance, #transactionLog) are inaccessible from outside the class at the syntax level — not just a convention. The getter 'balance' provides controlled read-only access.
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
speak() { return `${this.name} barks`; }
}
class ServiceDog extends Dog {
constructor(name, task) {
super(name);
this.task = task;
}
speak() {
return super.speak() + ` and is trained to ${this.task}`;
}
}
const buddy = new ServiceDog("Buddy", "guide the blind");
console.log(buddy.speak());
console.log(buddy instanceof ServiceDog); // true
console.log(buddy instanceof Dog); // true
console.log(buddy instanceof Animal); // true
console.log(buddy instanceof Array); // falsesuper.speak() delegates to the parent's implementation, allowing composition of behavior up the chain. instanceof checks the entire prototype chain, so a ServiceDog is also a Dog and an Animal.
Quick Quiz
1. What is required before using 'this' in a subclass constructor?
2. Which of the following correctly defines a private field?
3. If Dog extends Animal and ServiceDog extends Dog, what does (new ServiceDog()) instanceof Animal return?
4. How do you call a parent class method that has been overridden in a subclass?
Was this lesson helpful?