Back to Blog
Advanced2025-01-02

JavaScript Prototypes and Inheritance: How Objects Really Work

Understand the prototype chain, how inheritance works under the hood, and the relationship between constructors, prototypes, and instances.

JavaScript's Inheritance Model

Unlike classical OOP languages (Java, C++), JavaScript uses prototypal inheritance. Objects inherit directly from other objects, not from classes.

Even with ES6 classes, prototypes are still how it works underneath!

Every Object Has a Prototype

When you access a property on an object, JavaScript: 1. Looks on the object itself 2. If not found, looks on its prototype 3. If not found, looks on the prototype's prototype 4. Continues until reaching null

This is the prototype chain.

``javascript const animal = { eats: true, walk() { console.log('Walking...'); } };

const rabbit = { jumps: true, __proto__: animal // rabbit's prototype is animal };

console.log(rabbit.jumps); // true (own property) console.log(rabbit.eats); // true (inherited from animal) rabbit.walk(); // "Walking..." (inherited method) `

The Prototype Property

Functions have a prototype property used when creating objects with new.

`javascript function User(name) { this.name = name; }

// Methods go on the prototype (shared by all instances) User.prototype.sayHello = function() { console.log('Hello, ' + this.name); };

const alice = new User('Alice'); const bob = new User('Bob');

alice.sayHello(); // "Hello, Alice" bob.sayHello(); // "Hello, Bob"

// Both share the same method console.log(alice.sayHello === bob.sayHello); // true

// The prototype chain console.log(alice.__proto__ === User.prototype); // true console.log(User.prototype.__proto__ === Object.prototype); // true console.log(Object.prototype.__proto__); // null (end of chain) `

Understanding the Relationships

` +----------------+ | User | (Constructor Function) |----------------| | prototype -----+---> User.prototype +----------------+ +------------------+ | | constructor ----->| | | sayHello() | | | __proto__ -------->Object.prototype | +------------------+ | ^ v | +----------------+ | new User | alice | | |----------------| | | name: 'Alice' | | | __proto__ -----+------------+ +----------------+ `

Prototype Methods

Object.create()

Create objects with a specific prototype:

`javascript const animal = { speak() { console.log(this.sound); } };

const dog = Object.create(animal); dog.sound = 'Woof!'; dog.speak(); // "Woof!"

// dog's prototype is animal console.log(Object.getPrototypeOf(dog) === animal); // true `

Object.getPrototypeOf() / Object.setPrototypeOf()

`javascript const proto = { greet() { return 'Hi!'; } }; const obj = {};

Object.setPrototypeOf(obj, proto); console.log(obj.greet()); // "Hi!" console.log(Object.getPrototypeOf(obj) === proto); // true `

hasOwnProperty()

Check if property exists on object itself (not inherited):

`javascript const animal = { eats: true }; const rabbit = Object.create(animal); rabbit.jumps = true;

console.log(rabbit.hasOwnProperty('jumps')); // true (own) console.log(rabbit.hasOwnProperty('eats')); // false (inherited) console.log('eats' in rabbit); // true (including inherited) `

ES6 Classes: Syntactic Sugar

Classes are just cleaner syntax over prototypes:

`javascript class Animal { constructor(name) { this.name = name; }

speak() { console.log(this.name + ' makes a sound'); } }

class Dog extends Animal { constructor(name, breed) { super(name); // Call parent constructor this.breed = breed; }

speak() { console.log(this.name + ' barks'); }

fetch() { console.log(this.name + ' fetches the ball'); } }

const rex = new Dog('Rex', 'German Shepherd'); rex.speak(); // "Rex barks" rex.fetch(); // "Rex fetches the ball"

// Under the hood, it's still prototypes! console.log(rex.__proto__ === Dog.prototype); // true console.log(Dog.prototype.__proto__ === Animal.prototype); // true `

Inheritance Patterns

Classical Pattern (ES6 Classes)

`javascript class Vehicle { constructor(wheels) { this.wheels = wheels; }

drive() { console.log('Driving on ' + this.wheels + ' wheels'); } }

class Car extends Vehicle { constructor() { super(4); } } `

Object Composition (Often Preferred)

`javascript // Instead of inheritance, compose behaviors const canWalk = { walk() { console.log('Walking...'); } };

const canSwim = { swim() { console.log('Swimming...'); } };

const canFly = { fly() { console.log('Flying...'); } };

// Create duck with multiple abilities const duck = Object.assign({}, canWalk, canSwim, canFly); duck.walk(); // "Walking..." duck.swim(); // "Swimming..." duck.fly(); // "Flying..." `

Common Mistakes

Mistake 1: Overwriting the Prototype

`javascript function User(name) { this.name = name; }

// BAD: Replaces entire prototype, loses constructor User.prototype = { sayHello() { console.log('Hello'); } };

// GOOD: Add to existing prototype User.prototype.sayHello = function() { console.log('Hello'); }; `

Mistake 2: Arrow Functions in Prototypes

`javascript function User(name) { this.name = name; }

// BAD: Arrow function doesn't have its own 'this' User.prototype.greet = () => { console.log(this.name); // 'this' is not the instance! };

// GOOD: Regular function User.prototype.greet = function() { console.log(this.name); // 'this' is the instance }; `

Summary

- JavaScript uses prototypal inheritance (objects inherit from objects) - Every object has a prototype (accessible via __proto__ or Object.getPrototypeOf) - Constructor functions have a prototype` property used for new instances - ES6 classes are syntactic sugar over prototype-based inheritance - Prototype chain: object -> prototype -> prototype's prototype -> ... -> null - Prefer composition over inheritance when possible