Skip to the notes
JSGroundwork
JSGroundwork handwritten · web dev
✎Playground→⌘Problems↻Review🔥Progress

Chapters

27 chapters
⌕
Beginner9›
B1Setup & mental modelB2Types & valuesB3Operators & flowB4Functions (first half)B5Objects & arrays (first half)B6DOM & eventsB7Basic asyncB8Errors & tools★Cheat page
Intermediate10›
I1Scope & functions, properlyI2Objects deeplyI3Prototypes & OOPI4Async, properlyI5Modules & toolingI6Regex, dates & APIsI7Error handlingI8Real-time connectionsI9Offline & storage★Cheat page
Advanced10›
A1Engine & memoryA2Advanced asyncA3MetaprogrammingA4Types & dataA5Patterns & architectureA6PerformanceA7SecurityA8EcosystemA9Testing★Cheat page
/ search[ ] chaptert top

JavaScript levels

1Beginner2Intermediate3Advanced

Ready to read

JSJavaScript⑂Git◎Interview prepΣDSA in JSSDSystem Design
More topics15›
</>HTML{ }CSS⚛ReactNNext.jsNeNest.jsTSTypeScriptNoNode.js🐳DockerDBSQL & Databases✓Testing🔒Web Security☁Cloud & DevOps◈GraphQL◆Redis☸Kubernetes
100%
I3

Prototypes & OOP

class is real syntax now — but it's still prototypes underneath, every time.

The prototype chain

Every object has an internal link to another object — its prototype — and property lookup that doesn't find a match walks that link outward, exactly like the scope chain walked outward in the last chapter. The chain ends at null.

rex (a Dog instance)
name"Rex"
breed"Labrador"
no "speak" here → check its prototype ↓
Dog.prototype
speakƒ ()
found it — lookup stops here
Object.prototype
toStringƒ ()
the chain's final stop before null
⚠ __proto__ and .prototype are not the same thing obj.__proto__ (or the modern Object.getPrototypeOf(obj)) is the link an instance follows. Dog.prototype is a plain object that becomes that link for every instance new Dog() creates. A function has a .prototype property; an object has a __proto__ link. They're related, never interchangeable.
function Animal(name) { this.name = name; }
Animal.prototype.speak = function () { return this.name + " makes a sound"; };

const rex = new Animal("Rex");
Object.getPrototypeOf(rex) === Animal.prototype;   // true
rex instanceof Animal;                              // true — checks exactly this chain
rex.hasOwnProperty("name");                          // true — set directly on rex
rex.hasOwnProperty("speak");                          // false — it's on the prototype, not rex itself

What new actually does

new Fn(...) is four steps, always, whether Fn is an old-style constructor function or a modern class:

  1. A brand-new, empty object is created.
  2. Its internal prototype link is set to Fn.prototype.
  3. Fn runs with this bound to that new object (the "new" row from the this-binding table).
  4. If Fn returns an object explicitly, that's the result instead — otherwise the new object from step 1 is returned automatically.
function myNew(Ctor, ...args) {
  const obj = Object.create(Ctor.prototype);        // steps 1 & 2
  const result = Ctor.apply(obj, args);              // step 3
  return typeof result === "object" && result !== null ? result : obj;  // step 4
}

function Dog(name) { this.name = name; }
Dog.prototype.speak = function () { return this.name + " barks"; };

const rex = myNew(Dog, "Rex");
console.log(rex.speak(), rex instanceof Dog);   // what happens?

"Rex barks" true — a hand-rolled new that behaves identically to the real keyword, because those are genuinely all four steps it performs. It's worth building once, because it turns "new is magic" into "new is Object.create plus a function call plus a return-value check."

class — the same four steps, with real syntax

class Shape {
  static count = 0;        // lives on the class itself, not on instances
  #id;                      // private field — declared up front, "#" is part of the name

  constructor(name) {
    this.name = name;
    this.#id = ++Shape.count;
  }

  get id() { return this.#id; }        // getters/setters, same as plain objects

  describe() {
    return this.name + " #" + this.id;
  }

  static reset() { Shape.count = 0; }  // called as Shape.reset(), never on an instance
}

class Circle extends Shape {
  constructor(radius) {
    super("Circle");                    // MUST run before "this" is usable at all
    this.radius = radius;
  }
  describe() {
    return super.describe() + " (r=" + this.radius + ")";   // extend, don't just replace
  }
}

const c1 = new Circle(5);
c1.describe();   // "Circle #1 (r=5)"
Rule Everything class does is still prototypes: methods land on Circle.prototype, not on each instance, and extends just wires up the prototype chain from the diagram above automatically. class is real, enforced syntax on top of the exact same machinery — not a different object model bolted on beside it.
⚠ #private is enforced by the parser, not by convention c1.#id written outside the class body isn't a runtime access-denied error — it's a SyntaxError at parse time, because #id simply isn't valid syntax anywhere the class hasn't declared it. It's a much harder guarantee than the old _id underscore-means-private convention, which was never actually enforced by anything.

Composition vs inheritance, and mixins

extends models "is-a" — a Circle is a Shape. Composition models "has-a" or "can-do" — building an object out of smaller pieces it holds or uses, rather than a class it descends from. Deep inheritance chains tend to get brittle (change a base class, every descendant feels it); most modern guidance leans composition first, inheritance only for a genuinely stable, narrow "is-a" relationship.

A mixin is the middle ground: a function that takes a base class and returns a new one with extra behavior bolted on — reusable across classes that don't otherwise share a family tree.

const Serializable = (Base) => class extends Base {
  serialize() { return JSON.stringify(this); }
};

class Point {
  constructor(x, y) { this.x = x; this.y = y; }
}
class SerializablePoint extends Serializable(Point) {}

const p = new SerializablePoint(1, 2);
console.log(p.serialize());   // what happens?

{"x":1,"y":2} — Point never mentions serialization at all. Serializable(Point) returns a brand-new anonymous class that extends Point, so SerializablePoint gets both its own fields and the mixed-in method, and the same Serializable mixin could wrap any other base class exactly the same way.

Practice this layer

Opens in the editor — write it, run it, and check it against real tests.

extends, super, and the chain5 tests · advanced
←previousObjects deeply↑ CovernextAsync, properly→