Prototype and Prototypal Inheritance in JavaScript


JavaScript is often described as a prototype-based language. JavaScript uses prototypal inheritance, unlike traditional object-oriented programming languages that use class-based inheritance. This concept is a cornerstone of JavaScript and helps developers understand how objects, functions, and inheritance work.

Prototype and Prototypal Inheritance in JavaScript

In this article, we’ll explore what prototypes are, how prototypal inheritance works in JavaScript, and how it differs from classical inheritance.


What is a Prototype?

In JavaScript, every object has an internal property called [[Prototype]], which refers to another object. This object is called the prototype. The prototype provides a mechanism by which objects can inherit properties and methods from other objects.

When you try to access a property or method on an object, JavaScript first looks at that object. If the property or method is not found on the object itself, JavaScript will look at its prototype (and continue up the prototype chain) until it finds the property or reaches the end of the chain (null).

Example of Prototypal Chain:

const animal = {
  eats: true
};

const dog = {
  barks: true
};

// Set animal as the prototype of dog
dog.__proto__ = animal;

console.log(dog.barks);  // Output: true (from dog object)
console.log(dog.eats);   // Output: true (inherited from animal)

In this example, dog doesn’t have the eats property, so JavaScript looks at the prototype (animal) and finds it there. This is the essence of prototypal inheritance.


How Prototypal Inheritance Works

In JavaScript, objects can inherit properties and methods from other objects through their prototype. This allows for property and method sharing across different objects. Prototypal inheritance enables JavaScript to reuse properties and methods without explicitly defining them for every object.

When JavaScript looks for a property or method:

  1. It first checks the object itself.
  2. If the property or method is not found, it checks the object's prototype.
  3. If the prototype doesn’t have the property or method, the search continues up the prototype chain.
  4. If the chain ends (i.e., the prototype is null), and the property or method is still not found, JavaScript returns undefined.

Example:

const vehicle = {
  wheels: 4,
  drive() {
    console.log('Driving...');
  }
};

const car = {
  brand: 'Toyota'
};

// Set vehicle as the prototype of car
car.__proto__ = vehicle;

console.log(car.brand);   // Output: Toyota (own property of car)
console.log(car.wheels);  // Output: 4 (inherited from vehicle)
car.drive();              // Output: Driving... (inherited from vehicle)

Here, the car object inherits properties and methods from the vehicle object. This is because the car object’s prototype points to vehicle, creating an inheritance relationship.


The Object.prototype

At the top of the prototype chain is Object.prototype. This is the base object from which all other objects in JavaScript inherit. For example, all objects in JavaScript have access to methods like toString() and hasOwnProperty() because they are defined on Object.prototype.

const obj = {};
console.log(obj.toString());           // Output: [object Object]
console.log(obj.hasOwnProperty('key'));  // Output: false

In this case, toString() and hasOwnProperty() are not defined on obj, but JavaScript finds them on Object.prototype.


Constructor Functions and Prototypes

In JavaScript, constructor functions are a common way to create objects, and they play a key role in prototypal inheritance. Constructor functions are special functions used to create and initialize objects, and every function in JavaScript has a prototype property, which defines what will be inherited by instances created by that constructor.

Example:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function() {
  console.log(`Hello, my name is ${this.name}`);
};

const john = new Person('John', 30);
const jane = new Person('Jane', 25);

john.greet();  // Output: Hello, my name is John
jane.greet();  // Output: Hello, my name is Jane

In this example:

  • Person is a constructor function.
  • Each new instance of Person (like john and jane) has access to the greet method because it’s defined on Person.prototype.

How the Prototype Chain Works with Constructors

Every object created using a constructor function has its [[Prototype]] linked to the constructor’s .prototype object. This is how JavaScript sets up the inheritance relationship.

  1. The john and jane objects inherit from Person.prototype.
  2. Person.prototype is also an object, so it inherits from Object.prototype.

The prototype chain for the john object looks like this:

john --> Person.prototype --> Object.prototype --> null

Object.create() for Inheritance

The Object.create() method allows you to create a new object with a specific prototype, without the need for a constructor function.

Example:

const animal = {
  eats: true
};

const dog = Object.create(animal);
dog.barks = true;

console.log(dog.barks);  // Output: true
console.log(dog.eats);   // Output: true (inherited from animal)

In this case, the dog object is created with animal as its prototype. This is a more straightforward way to create inheritance relationships without using constructor functions.


Prototypal Inheritance vs Classical Inheritance

In classical (class-based) inheritance, objects are instantiated from classes, and inheritance occurs when one class inherits from another. Most traditional object-oriented languages like Java, C++, and C# follow this model.

JavaScript, however, uses prototypal inheritance, where objects can directly inherit from other objects without needing classes. Each object can have a prototype, and properties and methods are shared along the prototype chain.

With the introduction of ES6, JavaScript introduced the class syntax, which is just syntactic sugar over JavaScript's prototypal inheritance.

Example of ES6 class:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const john = new Person('John', 30);
john.greet();  // Output: Hello, my name is John

While this looks like classical inheritance, behind the scenes, JavaScript still uses prototypal inheritance. The class syntax makes it easier for developers coming from traditional OOP languages to work with JavaScript, but the underlying mechanism remains the same.


Modifying Prototypes

You can modify prototypes at any time. This allows you to add new methods to an object’s prototype, and all instances of that object will automatically have access to the new methods.

Example:

Person.prototype.sayAge = function() {
  console.log(`I am ${this.age} years old.`);
};

john.sayAge();  // Output: I am 30 years old
jane.sayAge();  // Output: I am 25 years old

By adding a sayAge method to Person.prototype, both john and jane can now use it, even though it was added after the instances were created.


The __proto__ vs prototype

  • __proto__: This is the internal property of an object that points to its prototype.
  • prototype: This is a property of constructor functions, used to set the prototype for objects created using the new keyword.

For example:

john.__proto__ === Person.prototype;  // true

john.__proto__ points to Person.prototype, creating the inheritance relationship.



Summary of Prototypal Inheritance

  • Every object in JavaScript has a prototype, which is another object that it inherits properties and methods from.
  • Prototypal inheritance allows objects to share functionality without duplicating code.
  • Constructor functions and the new keyword are commonly used to create objects with prototypes.
  • ES6 class syntax is syntactic sugar over prototypal inheritance.
  • Objects inherit from other objects through the prototype chain, allowing JavaScript to reuse properties and methods across instances.

Prototypal inheritance is a flexible and powerful system that sets JavaScript apart from traditional class-based languages. By understanding prototypes, you can unlock a deeper understanding of how JavaScript works under the hood and write more efficient, modular code.


Recommended Posts