# Classes

## 1. Classes Are Syntactic Sugar Over Prototypes

A `class` in JavaScript compiles down to the exact same constructor-function-plus-prototype mechanism from the Prototypes topic. It does not introduce a new object model — it introduces cleaner **syntax** for one that already existed.

```js
class Dog {
  constructor(name) {
    this.name = name;
  }
  bark() {
    return `${this.name} says woof`;
  }
}

// Equivalent, pre-class syntax:
function DogFn(name) { this.name = name; }
DogFn.prototype.bark = function () { return `${this.name} says woof`; };
```

`bark` lives on `Dog.prototype`, shared by every instance — identical to the manual prototype version.

## 2. Anatomy of a Class

```js
class BankAccount {
  static bankName = "JS National Bank"; // shared across ALL instances, not per-instance
  #balance;                              // private field — only accessible inside this class

  constructor(owner, initialBalance = 0) {
    this.owner = owner;
    this.#balance = initialBalance;
  }

  deposit(amount) {                      // instance method — on the prototype
    this.#balance += amount;
    return this.#balance;
  }

  get balance() {                        // getter — accessed like a property, not called
    return this.#balance;
  }

  static create(owner) {                 // static method — called on the class, not instances
    return new BankAccount(owner);
  }
}
```

| Feature | Syntax | Lives on | Shared or per-instance |
|---|---|---|---|
| Instance field | `field = value;` | instance | per-instance |
| Private field | `#field;` | instance | per-instance, truly inaccessible outside |
| Instance method | `method() {}` | prototype | shared |
| Static field/method | `static x = ...` | the class itself | shared, one copy total |
| Getter/setter | `get x() {}` / `set x(v) {}` | prototype | shared, accessed as a property |

## 3. `#private` Fields Are Real Privacy

Unlike closures-based privacy, `#field` access outside the class is a **syntax error**, not just convention:

```js
class Secret {
  #value = 42;
  reveal() { return this.#value; }
}
const s = new Secret();
console.log(s.reveal()); // 42
console.log(s.#value);   // SyntaxError — # fields aren't just "hidden", they're inaccessible syntax
```

## 4. Inheritance with `extends` and `super`

```js
class Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} makes a sound`; }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);           // MUST call super() before using `this` in a derived constructor
    this.breed = breed;
  }
  speak() {                // overrides Animal's speak
    return `${super.speak()}, specifically a bark`; // super.method() calls the parent's version
  }
}

const rex = new Dog("Rex", "Labrador");
console.log(rex.speak()); // "Rex makes a sound, specifically a bark"
console.log(rex instanceof Animal); // true
```

`extends` links `Dog.prototype`'s prototype to `Animal.prototype` — extending the prototype chain by one link, exactly as in the Prototype Chain topic.

![rex links to Dog.prototype, which links to Animal.prototype via extends](diagrams/01-extends-chain.svg)

## 5. Class Methods Are Not Hoisted Like Function Declarations

```js
const early = new Foo(); // ReferenceError — classes are NOT hoisted the way function declarations are
class Foo {}
```

Classes are hoisted (their name is reserved) but remain in the **Temporal Dead Zone** until the declaration line runs — same rule as `let`/`const`.

## 6. Under the Hood: Field Order and Hidden Class Stability

Everything from the Prototypes topic's "Under the Hood" section (hidden classes, transition trees, inline caches) applies directly to class instances — `class` is syntax for exactly that machinery, so writing a class well or poorly has a direct, measurable performance consequence, not just a stylistic one.

### Why declaring fields in a fixed order matters

```js
class PointGood {
  x; y; // declared as class fields, in a FIXED order, before the constructor even runs
  constructor(x, y) { this.x = x; this.y = y; }
}

class PointBad {
  constructor(x, y, includeZ) {
    this.x = x;
    this.y = y;
    if (includeZ) this.z = 0; // conditionally adds a property — only SOME instances get it
  }
}
```

Every `PointGood` instance transitions through the exact same hidden-class sequence — `{}` → `{x}` → `{x,y}` — because the fields are always added in the same order. Every `PointBad` instance created with `includeZ = true` ends up on a *different* hidden class than one created with `includeZ = false`, even though they're the same class. Call sites that operate on both end up polymorphic (or worse) instead of monomorphic, exactly as described in the Prototype Chain topic — the fix is declaring all fields up front (as class fields, or unconditionally in the constructor) so every instance follows one predictable transition path.

### `#private` fields are not just "hidden" — they're a different storage mechanism entirely

Public fields participate in the ordinary hidden-class/property system described above. `#private` fields are specified and implemented as a **separate mechanism** — conceptually closer to a `WeakMap` keyed by instance (though real engines optimize this further) than to a regular own property. This is *why* `#value` can't be reached via `Object.keys`, bracket notation, `Reflect.ownKeys`, or any of the normal property-introspection tools from the Objects topic — it was never a property of the object in the same sense `x` and `y` are; it's stored through an entirely separate, syntax-gated channel that only code lexically inside the class body can address at all.

### Methods are added to the prototype exactly once, ever

```js
class Dog {
  bark() { return "woof"; } // this function object is created ONCE, when the class is defined
}
```

Unlike a field initializer (which runs per-instance, in the constructor), `bark` is attached to `Dog.prototype` a single time, when the class declaration itself is evaluated — every instance shares the literal same function object, which is both why it's memory-efficient (see the Prototypes topic) and why mutating `Dog.prototype.bark` after the fact affects every existing and future instance simultaneously.

## 7. Common Pitfalls

1. **Forgetting `super()` in a derived class constructor** — accessing `this` before calling `super()` throws a `ReferenceError`. In a derived class, `this` doesn't exist until `super()` runs.
2. **Class methods are not auto-bound** — detaching a method (`const fn = instance.method`) loses `this`, exactly like regular prototype methods (see the `this` keyword topic). Use arrow class fields if you need auto-binding.
3. **Static members are not inherited to instances** — `BankAccount.bankName` works; `accountInstance.bankName` does not, unless accessed via `this.constructor.bankName`.
4. **Class bodies are always strict mode** — `this` in a detached method call is `undefined`, never the global object, regardless of the surrounding file's mode.
5. **Forgetting classes aren't hoisted like functions** — you cannot instantiate a class before its declaration line, even though the name is reserved earlier.

## 8. Quick Reference

```
class X extends Y {
  static s = ...       // one per class
  #private = ...        // one per instance, truly private
  constructor() { super(); }  // must call super() first if extending
  method() {}            // shared via X.prototype
  get/set prop() {}       // accessed as a property
}
```
