# Prototypes — Part 2: Shared Methods and Practical Patterns

## 1. Why Prototypes Save Memory

Methods placed on a shared prototype exist **once in memory**, no matter how many instances are created — unlike defining a method inside the constructor, which creates a new function object per instance.

```js
// BAD: new function created for every single Dog instance
function DogWasteful(name) {
  this.name = name;
  this.bark = function () { return `${this.name} says woof`; }; // duplicated per instance
}

// GOOD: one shared function on the prototype
function Dog(name) { this.name = name; }
Dog.prototype.bark = function () { return `${this.name} says woof`; };

const dogs = Array.from({ length: 10000 }, (_, i) => new Dog(`dog${i}`));
// Wasteful version: 10000 separate bark function objects
// Prototype version: 1 shared bark function, referenced by all 10000
```

## 2. The Prototype Chain Terminates at `null`

```js
console.log(Object.getPrototypeOf({}));                // Object.prototype
console.log(Object.getPrototypeOf(Object.prototype));   // null — the end of the chain
```

Every plain object's chain eventually reaches `Object.prototype` (which provides `toString`, `hasOwnProperty`, etc.), then `null`.

```js
const bare = Object.create(null); // no prototype at all — not even Object.prototype
console.log(bare.toString); // undefined — no inherited methods whatsoever
```

`Object.create(null)` is used for objects meant to be pure key-value dictionaries, immune to prototype pollution attacks.

## 3. Overriding an Inherited Method

```js
function Animal() {}
Animal.prototype.speak = function () { return "..."; };

function Cat() {}
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.speak = function () { return "Meow"; }; // shadows Animal's version

const c = new Cat();
console.log(c.speak()); // "Meow"
```

This is exactly how method overriding works in class-based inheritance too — `class` syntax just automates this wiring.

## 4. Checking the Chain

```js
console.log(rex instanceof Dog);      // true — Dog.prototype is somewhere in rex's chain
console.log(Dog.prototype.isPrototypeOf(rex)); // true, equivalent check
console.log(Object.getPrototypeOf(rex) === Dog.prototype); // true, direct link only
```

`instanceof` walks the **entire** chain; `Object.getPrototypeOf` only gives you the **immediate** link.

## 5. Common Pitfalls

1. **Mutating a shared prototype object accidentally** — `Dog.prototype.tricks = []` creates ONE array shared by every `Dog` instance, not a per-instance array. Always initialize per-instance state (arrays, objects) inside the constructor.
2. **Confusing `Fn.prototype` with `Object.getPrototypeOf(instance)`** — they refer to the same object, but from different sides of the relationship; mixing them up is extremely common when debugging.
3. **Using `for...in` without `hasOwnProperty` checks** — iterates inherited enumerable properties too, which can pull in unexpected keys if the prototype has been extended.
4. **Long prototype chains hurting lookup performance** — every miss walks one more link; deeply nested inheritance (5+ levels) measurably slows property access in hot code paths.
5. **Reassigning `Fn.prototype` entirely after instances already exist** — existing instances keep their old prototype link; only instances created *after* the reassignment get the new one.

## 6. Quick Reference

```
instance.__proto__  ===  Constructor.prototype
instance.hasOwnProperty(key)   → true only for own properties
key in instance                → true for own OR inherited
Object.keys(instance)          → own enumerable keys only
for...in instance               → own AND inherited enumerable keys
```
