You are currently viewing JavaScript Prototypes: How Inheritance Works
JavaScript Prototypes

JavaScript Prototypes: How Inheritance Works

JavaScript Prototypes

In the world of JavaScript, understanding how inheritance works is essential for writing clean, efficient, and maintainable code. Unlike class-based languages such as Java or C++, JavaScript uses a unique system called prototype-based inheritance. This concept often confuses beginners, but once grasped, it becomes a powerful tool in your JavaScript toolkit.

This article will take a deep dive into JavaScript Prototypes: How Inheritance Works, starting from the basics and building up to advanced use cases and real-world applications. By the end, you’ll not only understand what prototypes are, but also how they empower inheritance in JavaScript.

JavaScript Modules: Import and Export Basics


What is Inheritance in Programming?

Before diving into JavaScript-specific concepts, let’s quickly review what inheritance means in general programming.

Inheritance allows one object to acquire the properties and methods of another. This promotes code reuse, avoids duplication, and makes the system easier to manage.

For example, in class-based languages:

class Animal {
    void makeSound() {
        System.out.println("Some sound");
    }
}

class Dog extends Animal {
    void bark() {
        System.out.println("Woof!");
    }
}

Here, Dog inherits from Animal and gains access to its methods.

But JavaScript does things differently.


JavaScript is Prototype-Based

In JavaScript, there are no traditional classes under the hood. Instead, JavaScript uses prototypes to enable inheritance. Every object in JavaScript has an internal link to another object called its prototype.

When you try to access a property on an object, and it doesn’t exist on that object, JavaScript automatically looks up the javacsript prototypes chain until it finds it or reaches the end (null).


Understanding the Prototype Chain

Let’s look at a simple example:

const person = {
  greet: function() {
    console.log('Hello!');
  }
};

const student = Object.create(person);

student.study = function() {
  console.log('Studying...');
};

student.greet(); // Output: Hello!

Here’s what happens:

  1. student is created using Object.create(person).
  2. student doesn’t have its own greet method.
  3. JavaScript looks up the prototype chain and finds greet in person.

This chain of lookup is called the prototype chain.


Every Object Has a Prototype

Behind the scenes, each object in JavaScript has a hidden property known as [[Prototype]], accessible through __proto__ (non-standard) or via Object.getPrototypeOf().

const car = {
  brand: 'Toyota'
};

console.log(car.__proto__); // Object prototype

When you create an object with a literal {}, it inherits from Object.prototype.


Constructor Functions and Prototypes

A more structured way of creating reusable objects in JavaScript is through constructor functions.

function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function() {
  console.log(`${this.name} makes a sound.`);
};

const dog = new Animal('Buddy');
dog.speak(); // Buddy makes a sound.

Explanation:

  • Animal is a constructor function.
  • When you call new Animal('Buddy'), JavaScript:
    • Creates a new object.
    • Sets its prototype to Animal.prototype.
    • Binds this to the new object.
    • Returns the object.
  • The speak method is not duplicated for each instance but shared via the prototype.

This is where true inheritance starts to shine.


Prototype Inheritance in Action

Let’s extend the Animal example to another level.

function Dog(name, breed) {
  Animal.call(this, name); // Inherit properties
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype); // Inherit methods
Dog.prototype.constructor = Dog; // Fix constructor reference

Dog.prototype.bark = function() {
  console.log(`${this.name} says Woof!`);
};

const myDog = new Dog('Max', 'Labrador');
myDog.speak(); // Max makes a sound.
myDog.bark();  // Max says Woof!

Breakdown:

  • Animal.call(this, name) copies the properties.
  • Object.create(Animal.prototype) sets up prototype-based method inheritance.
  • Dog.prototype.constructor = Dog fixes the constructor property, which was overwritten.

This is how prototypal inheritance works in ES5-style JavaScript.


ES6 Classes and Prototypes

ES6 introduced the class syntax to make JavaScript inheritance easier to write and read. But under the hood, it’s still prototype-based.

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;
  }

  bark() {
    console.log(`${this.name} barks!`);
  }
}

const rex = new Dog('Rex', 'German Shepherd');
rex.speak(); // Rex makes a sound.
rex.bark();  // Rex barks!

Behind the scenes, this syntax still creates javascript prototypes and links them. It’s just syntactic sugar over the same old javascript prototype mechanism.


Prototype Chain Visualization

Let’s walk through a prototype chain:

const bird = new Animal('Tweety');
  1. bird → instance of Animal
  2. bird.__proto__Animal.prototype
  3. Animal.prototype.__proto__Object.prototype
  4. Object.prototype.__proto__null (end of chain)

When you call bird.speak(), JavaScript does the following:

  • Looks for speak in bird
  • Not found? Goes to bird.__proto__
  • Found in Animal.prototype → executes

If it were not found even there, it would continue searching up the chain until it hits null.


Advantages of Prototypal Inheritance

  1. Memory Efficiency: Shared methods are not duplicated in every instance.
  2. Dynamic Extension: You can add methods to the prototype after objects are created.
  3. Flexibility: Objects can inherit from multiple sources with Object.assign() or mixins.

Disadvantages of Prototypal Inheritance

  1. Complexity: Understanding and debugging the prototype chain can be confusing.
  2. Verbose Syntax: Pre-ES6 inheritance is clunky.
  3. Inheritance Issues: Overriding and chaining can be tricky if not done properly.

Custom Prototype Chains (Advanced Use)

You can manually set up complex chains:

const grandparent = { a: 1 };
const parent = Object.create(grandparent);
const child = Object.create(parent);

console.log(child.a); // 1

In this case:

  • child inherits from parent
  • parent inherits from grandparent
  • You can create deep inheritance trees, though this can get hard to manage.

Checking Inheritance and Prototypes

JavaScript provides tools to check prototype relationships:

console.log(Dog.prototype.isPrototypeOf(myDog)); // true
console.log(Animal.prototype.isPrototypeOf(myDog)); // true
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Animal); // true

These are useful for debugging or writing flexible code that depends on object types.


Best Practices for Using Prototypes

  1. Favor composition over deep inheritance where possible.
  2. Use class syntax for clarity and maintainability.
  3. Don’t over-engineer—use prototypes when shared behavior makes sense.
  4. Be cautious when extending built-in prototypes like Array.prototype or Object.prototype.

Common Misconceptions About Prototypes

❌ Prototypes are copies

Truth: They are links. Nothing is copied. All instances point to the same prototype object.

❌ Each object has its own prototype methods

Truth: All instances share the methods defined on the constructor’s prototype.

❌ The constructor property is always correct

Truth: When you override a prototype, you must manually reset the constructor.


Conclusion

JavaScript’s prototype-based inheritance system is a powerful and flexible way to enable object sharing and code reuse. While it may seem complex at first, understanding the javascript prototype chain, how __proto__ and Object.create() work, and how inheritance is set up via constructors or class syntax can give you a big advantage as a developer.

Here’s a final recap of the key points:

  • JavaScript inheritance is prototype-based, not class-based.
  • Every object has a hidden [[Prototype]] (often accessed via __proto__).
  • Use Object.create() for clean prototype inheritance.
  • ES6 class and extends make syntax easier but still use javascript prototypes internally.
  • Inheritance enables code reuse, reduces duplication, and creates relationships between objects.

Once you master javascript prototypes, you unlock a core part of how JavaScript works. And with that, you can write more elegant, scalable, and robust applications.


Let me know if you want this article formatted for blog publishing (with meta tags, slug, etc.) or need a PDF version.