JavaScript OOP

Prototypes, Classes & Inheritance

JavaScript's OOP isn't class-based underneath — it's prototype-based. class is real syntax that compiles down to the same prototype chain you'd build by hand.

ES2023 6 Topics
01

The Prototype Chain

Every object has an internal link to another object, its prototype. When you access a property JS doesn't find on the object itself, it walks up the chain until it finds it or hits null. This is how methods are shared without copying them onto every instance.

JavaScript — Prototypes by Hand
function Animal(name) { this.name = name; }
Animal.prototype.speak = function() {
  return `${this.name} makes a sound`;
};

const dog = new Animal("Rex");
dog.speak();          // "Rex makes a sound" — found via the prototype
dog.hasOwnProperty("name");    // true  — set directly on the instance
dog.hasOwnProperty("speak");   // false — lives on Animal.prototype
Object.getPrototypeOf(dog) === Animal.prototype;  // true
02

Classes

class (ES2015) is cleaner syntax over the exact same prototype mechanism above — no new capability, just a familiar shape for people coming from class-based languages.

JavaScript — class Syntax
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {                       // still added to Animal.prototype under the hood
    return `${this.name} makes a sound`;
  }
}

const cat = new Animal("Whiskers");
cat.speak();       // "Whiskers makes a sound"
typeof Animal;       // "function" — classes are still functions
Class bodies run in strict mode automatically, and unlike function declarations, class declarations are not hoisted for use — a class must be defined before it's referenced.
03

Encapsulation — Private Fields

Before ES2022, "private" was a naming convention (_balance) that JS never enforced. The # prefix now creates a field that's genuinely inaccessible from outside the class.

JavaScript — # Private Fields
class BankAccount {
  #balance = 0;               // truly private — not just convention

  deposit(amount) {
    if (amount <= 0) throw new Error("Invalid amount");
    this.#balance += amount;
    return this.#balance;
  }
  get balance() { return this.#balance; }   // controlled read access
}

const acc = new BankAccount();
acc.deposit(100);
acc.balance;         // 100 — via the getter
// acc.#balance;     // SyntaxError — inaccessible outside the class
04

Inheritance

JavaScript — extends & super
class Dog extends Animal {
  constructor(name, breed) {
    super(name);              // must run before using `this`
    this.breed = breed;
  }
  speak() {                        // override
    return super.speak() + " — specifically, barks";
  }
}

const rex = new Dog("Rex", "Lab");
rex.speak();               // "Rex makes a sound — specifically, barks"
rex instanceof Animal;   // true
JavaScript only supports single inheritance via extends. For sharing behavior across unrelated classes, use mixins (a function that returns a class extending its argument) instead.
05

Polymorphism

Because JS is dynamically typed, polymorphism needs no interface or annotation — any object with a matching method shape (duck typing) works, related by inheritance or not.

JavaScript — Duck Typing
class Cat { speak() { return "Meow"; } }
class Robot { speak() { return "BEEP"; } }   // unrelated to Animal

function makeItTalk(thing) {
  return thing.speak();      // works for ANY object with a .speak() method
}

[new Dog("Rex"), new Cat(), new Robot()].map(makeItTalk);
// each calls its own version of speak() — no shared base class required
06

Static Members & Getters/Setters

JavaScript — static, get, set
class Circle {
  static unit = new Circle(1);        // belongs to the class, not instances

  constructor(radius) { this._radius = radius; }

  static fromDiameter(d) {           // factory — Circle.fromDiameter(10)
    return new Circle(d / 2);
  }
  get area() { return Math.PI * this._radius ** 2; }
  set radius(r) {
    if (r < 0) throw new Error("radius can't be negative");
    this._radius = r;
  }
}

const c = Circle.fromDiameter(10);
c.area;              // reads like a property, runs like a method
c.radius = 7;         // runs the setter's validation

Quick Quiz

1. Under the hood, class syntax compiles to…
2. What makes a class field genuinely private?
3. Inside a subclass constructor, super(...) must run…
4. JS achieves polymorphism across unrelated classes via…
5. A static class member belongs to…