Prototypes & Inheritance
Understand JavaScript's prototype-based inheritance model that underpins classes, frameworks, and the language itself.
The Prototype Chain
Every JavaScript object has an internal `[[Prototype]]` link (accessible via `Object.getPrototypeOf` or `__proto__`). Property lookup walks this chain until a match is found or the chain ends at `null`.
This delegation model means objects share behavior through linked prototypes rather than copying methods. Arrays inherit from `Array.prototype`, which is why all arrays have access to `.map()` and `.filter()` without defining them individually.
- Objects delegate to their prototype for lookups
- Chain ends at `null`
- Shared methods live on the prototype object
const arr = [1, 2, 3]; Object.getPrototypeOf(arr) === Array.prototype; // true
Constructor Functions
Before ES6 classes, constructor functions combined with `new` were the standard way to create typed objects. Methods were attached to `Constructor.prototype` so all instances shared one function object per method.
This pattern still appears in older libraries and in transpiled output. Reading `function User() {}` plus `User.prototype.save = function() {}` should feel as natural as reading `class User { save() {} }`.
- `new` links instance to constructor.prototype
- Methods on prototype, data on instance
- Still visible in legacy and compiled code
function User(name) {
this.name = name;
}
User.prototype.greet = function() {
return `Hi, ${this.name}`;
};Object.create and Composition
`Object.create(proto)` creates an object with an explicit prototype, enabling fine-grained inheritance without constructor functions. This is useful for object factories and for implementing delegation-based patterns.
Many teams prefer composition over deep inheritance hierarchies. Mixins, factory functions, and plain object spread often produce more maintainable code than multi-level prototype chains, especially in application logic rather than framework internals.
- `Object.create` for explicit prototype link
- Favor composition over deep inheritance
- Mixins and factories as alternatives
const canEat = { eat(food) { return `eating ${food}`; } };
const dog = Object.create(canEat);
dog.eat('kibble');Classes as Syntactic Sugar
ES6 `class` syntax defines a constructor and methods that desugar to prototype assignment. `extends` sets up the prototype chain between constructor functions and provides `super` for parent method calls.
Static methods live on the constructor itself. Private fields (`#field`) and static blocks are modern additions that address real encapsulation gaps in the prototype model. Knowing the desugaring helps when debugging transpiled bundles.
- Classes desugar to constructors + prototypes
- `extends` wires prototype chain and `super`
- Private fields use `#` syntax
class Animal {
constructor(name) { this.name = name; }
speak() { return '...'; }
}
class Dog extends Animal {
speak() { return `${this.name} barks`; }
}When to Use Which Pattern
Use classes when modeling entities with shared behavior and clear type hierarchies — domain models, UI component bases, and service abstractions. Use factory functions and closures when you need lightweight objects without inheritance ceremony.
Avoid prototype manipulation in application code unless you are building a library or polyfill. Directly changing built-in prototypes (`Array.prototype.myMethod`) is considered harmful because it affects all code in the runtime.
- Classes for entity modeling
- Factories for simple object creation
- Never modify built-in prototypes in apps