Advance JavaScript (DOM, BOM, Web API) / Object Oriented Programming

[[Prototypes]] and Inheritance

In JavaScript, [[Prototype]] is a built-in object that every function and object gets.

It is used to share properties and methods so they are not copied for every object — saving memory.

[[Prototype]] is the foundation of OOP in JavaScript. Unlike class-based languages (Java, C++), JS uses:

✅ prototype-based inheritance

❌ not classical inheritance


🧠 What is a Prototype?

In JavaScript, objects can inherit features from one another via their internal [[Prototype]].

Every object has an internal hidden property called [[Prototype]] (often accessed as __proto__). Because [[Prototype]] is itself an object, it has its own [[Prototype]].

This forms what is known as the prototype chain. The chain ends when an object’s [[Prototype]] is null.

✅ Simple Example

Suppose you have an object person with a property called name:

let person = {'name' : 'John'}

When examining the person object in the console, you’ll find that the person object has a property called prototype denoted by the [[Prototype]]:

The [[Prototype]] itself is an object with its own properties:

✅ How Property Lookup Works

When you access a property of an object, if the object has that property, it’ll return the value.

Example:

let person = {'name' : 'John'}
console.log(person.name)

It returns "John" as expected.

However, if you try to access a property that doesn’t exist, the JavaScript engine will:

  1. Search in the object’s [[Prototype]]

  2. If not found, search the prototype’s prototype

  3. Continue until:

    • the property is found ✅

    • or reaches the end of the chain (null) ❌

For example, calling:

person.toString();

The toString() method returns a string representation of the object — by default:

[object Object]

Since the person object doesn’t have toString(), JavaScript searches in its prototype and finds it there.

A method is simply a property whose value is a function.


✅ Prototypal inheritance

In programming, we often want to take something and extend it.

For instance, we have a user object with its properties and methods, and want to make admin and guest as slightly modified variants of it. We’d like to reuse what we have in user, not copy/reimplement its methods, just build a new object on top of it.

Prototypal inheritance is a language feature that helps in that.


The property [[Prototype]] is internal and hidden, but there are many ways to set it.

One of them is to use the special name __proto__, like this:

let animal = {
  eats: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal; // (*)

// we can find both properties in rabbit now:
console.log( rabbit.eats ); // true (**)
console.log( rabbit.jumps ); // true

Now if we read a property from rabbit, and it’s missing, JavaScript will automatically take it from animal.

  • Here the line (*) sets animal to be the prototype of rabbit.

  • Then, when console tries to read property rabbit.eats (**), it’s not in rabbit, so JavaScript follows the [[Prototype]] reference and finds it in animal (look from the bottom up).

  • Here we can say that “animal is the prototype of rabbit” or “rabbit prototypically inherits from animal”.

  • So if animal has a lot of useful properties and methods, then they become automatically available in rabbit. Such properties are called “inherited”.

If we have a method in animal, it can be called on rabbit:

let animal = {
  eats: true,
  walk() {
    alert("Animal walk");
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

// walk is taken from the prototype
rabbit.walk(); // Animal walk

The method is automatically taken from the prototype, like this:


The prototype chain can be longer:

let animal = {
  eats: true,
  walk() {
    alert("Animal walk");
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

let longEar = {
  earLength: 10,
  __proto__: rabbit
};

// walk is taken from the prototype chain
longEar.walk(); // Animal walk
alert(longEar.jumps); // true (from rabbit)

Now if we read something from longEar, and it’s missing, JavaScript will look for it in rabbit, and then in animal.

There are only two limitations:

  1. The references can’t go in circles. JavaScript will throw an error if we try to assign __proto__ in a circle.

  2. The value of __proto__ can be either an object or null. Other types are ignored.


✅ Modern Prototype Methods

Setting or reading the prototype with obj.__proto__ is considered outdated and somewhat deprecated (moved to the so-called “Annex B” of the JavaScript standard, meant for browsers only).

The modern methods to get/set a prototype are:

  • Object.getPrototypeOf(obj) – returns the [[Prototype]] of obj.

  • Object.setPrototypeOf(obj, proto) – sets the [[Prototype]] of obj to proto.

The only time using __proto__ is acceptable is when you’re creating an object literal, like this:

let rabbit = {
  __proto__: animal
};

Although, there’s a special method for this too:

Object.create(proto[, descriptors]) – creates an empty object with given proto as [[Prototype]] and optional property descriptors.

For instance:

let animal = {
  eats: true
};

// create a new object with animal as a prototype
let rabbit = Object.create(animal); // same as {__proto__: animal}

alert(rabbit.eats); // true

alert(Object.getPrototypeOf(rabbit) === animal); // true

Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}

The Object.create method is a bit more powerful, as it has an optional second argument: property descriptors.

We can provide additional properties to the new object there, like this:

let animal = {
  eats: true
};

let rabbit = Object.create(animal, {
  jumps: {
    value: true
  }
});

alert(rabbit.jumps); // true

jumps: { value: true} gets stored in rabbit object itself, not prototype (animal)