Technology · JavaScript

Advanced

JavaScript Prototypal Inheritance

Understand the prototype chain, Object.create, and how class syntax wraps prototypal inheritance.

TL;DR
  1. 01Objects inherit properties through a linked chain of prototypes.
  2. 02Use Object.create to set an object's prototype directly.
  3. 03Class syntax is sugar over the same prototype mechanism.

The Prototype Chain

  • Every object links to another object called its prototype, forming a chain.
    const animal = { eats: true };
    const rabbit = Object.create(animal);
    rabbit.hops = true;
    rabbit.eats; // true — found via the prototype chain
    
  • Property lookups walk up the chain until the property is found or the chain ends.
  • The chain ends at Object.prototype, whose own prototype is null.
  • Methods like .toString() come from Object.prototype, inherited by nearly everything.
  • Writing a property always creates it on the object itself, never on the prototype.
  • Reading a missing property returns undefined only after the whole chain is checked.

Creating and Inspecting Prototypes

  • Use Object.create(proto) to make a new object with a specific prototype directly.
    const base = { greet() { return 'hi'; } };
    const obj = Object.create(base);
    obj.greet(); // 'hi'
    
  • Pass null to Object.create(null) to build an object with no prototype at all.
  • Use Object.getPrototypeOf(obj) to read an object's current prototype safely.
    Object.getPrototypeOf(obj) === base; // true
    
  • Use Object.setPrototypeOf(obj, proto) to change a prototype after creation.
  • Avoid __proto__ in new code; it works but is a legacy accessor, not a clean API.
  • Object.create is the most direct way to model inheritance without constructors.

Constructor Functions and prototype

  • Every function has a prototype property used as the prototype for objects it constructs.
    function Dog(name) { this.name = name; }
    Dog.prototype.bark = function () { return `${this.name} barks`; };
    const rex = new Dog('Rex');
    rex.bark(); // 'Rex barks'
    
  • Calling a function with new creates an object linked to that function's prototype.
  • Methods defined on prototype are shared by every instance, saving memory.
  • Instance-specific data, like name, belongs on this inside the constructor, not the prototype.
  • Check rex instanceof Dog to confirm Dog.prototype is in rex's prototype chain.
  • This pattern is the original way JavaScript implemented inheritance before class.

Class Syntax as Prototype Sugar

  • A class declaration creates a constructor function with methods on its prototype, just like the manual pattern.
    class Dog {
      constructor(name) { this.name = name; }
      bark() { return `${this.name} barks`; }
    }
    typeof Dog; // 'function'
    
  • extends sets up the prototype chain between parent and child classes automatically.
    class Puppy extends Dog {
      bark() { return `${super.bark()} (puppy style)`; }
    }
    
  • super.method() inside a subclass calls the parent's version via the prototype chain.
  • Class methods are non-enumerable by default, unlike properties added with plain assignment.
  • Class bodies run in strict mode and are not hoisted like function declarations.
  • Despite the new syntax, Puppy.prototype is still a plain object linked to Dog.prototype.

Own Properties vs Shadowing

  • An own property lives directly on the object; an inherited one lives on its prototype.
    const base = { color: 'red' };
    const item = Object.create(base);
    item.hasOwnProperty('color'); // false
    
  • Assigning to item.color creates a new own property that shadows the inherited one.
    item.color = 'blue';
    item.color; // 'blue' — own property wins
    base.color; // 'red' — unchanged
    
  • Use Object.hasOwn(obj, key) as the modern, safer alternative to hasOwnProperty().
  • Shadowing never deletes or modifies the prototype's original property.
  • Use delete item.color to remove the own property and reveal the inherited one again.
  • for...in enumerates inherited properties too; filter with hasOwnProperty() when needed.
Tips
  1. 01Use Object.getPrototypeOf() and Object.setPrototypeOf() instead of __proto__, since they are the standardized and faster API.
  2. 02Call hasOwnProperty() when you need to check a property's own existence without accidentally matching an inherited one.
Warnings
  1. 01Setting __proto__ repeatedly deoptimizes property access in most JavaScript engines, so prefer Object.create at creation time.
  2. 02A for...in loop walks inherited enumerable properties too, so unfiltered loops can leak prototype methods into your iteration unexpectedly.
FAQ
JavaScript Optional ChainingJavaScript Proxy and Reflect