JavaScript Prototypal inheritance

In programming, it is often necessary to take something and extend it.

For example, there is a user object with its methods and properties. You intend to make admin and guest as pretty modified variants of it. So you wish to reuse what you have in the user, building a new object on top of it.

The prototypal inheritance feature is there to help you.

[[Prototype]]

JavaScript objects have a unique hidden property [[Prototype]]. It is either null or references another object. The object is named “a prototype.”

The prototype is a super-useful thing. When one wants to read a property from the object, and it’s missing, JavaScript takes it from the prototype. This action is called “prototypal inheritance” in programming.

The property [[Prototype]] can be internal and hidden. There is a variety of ways to set it.

One such way is using the unique name __proto__.

For instance:

let animal = {
  speaks: true
};
let dog = {
  runs: true
};
dog.__proto__ = animal;

Please, take into account that __proto__ is not the same as [[Prototype]]: it’s a getter/setter for it. The __proto__ might only be supported by browsers, but all the environments, including the server-side, support it.

If you search for a property in the dog, and it’s missing, JavaScript takes it from the animal:

let animal = {
  speaks: true
};
let dog = {
  runs: true
};
dog.__proto__ = animal; // (*
// we can find both properties in dog now:
console.log(dog.speaks); // true (**)
console.log(dog.runs); // true

In the example above, the line (*) sets the animal to be a prototype of the dog.

So, we can note that the animal is the prototype of the dog. If the first has many valuable properties and methods, they become automatically available in the second one. Properties, like this, are known as “inherited.”

In case you have a method in the animal, you can call it on the dog.

For instance:

let animal = {
  speaks: true,
  walk() {
    console.log("Animal walks");
  }
};
let dog = {
  runs: true,
  __proto__: animal
};
dog.walk(); // Animal walks,walk is taken from the prototype

The prototype chain might be longer.

For example:

let animal = {
  speaks: true,
  walk() {
    console.log("Animal walks");
  }
};
let dog = {
  runs: true,
  __proto__: animal
};
let animalWeight = {
  weight: 30,
  __proto__: dog
};
animalWeight.walk(); // Animal walks, walk is taken from the prototype chain
console.log(animalWeight.runs); // true (from dog)

Only two limitations can be distinguished:

  1. It is not possible to implement the references in circles. An error will occur, in case you try to assign __proto__ in a circle.
  2. The value of __proto__ might be either null or an object. Any other type is ignored.

Writing Doesn’t Use Prototype

You can use prototype only for reading properties. The operations like write or delete operate directly with the object.

Let’s see the following example:

let animal = {
  speaks: true,
  walk() {
    /* this method doesn’t be used by dog */
  }
};
let dog = {
  __proto__: animal
};
dog.walk = function () {
  console.log("Dog starts run!");
};
dog.walk(); // Dog starts run!

The dog.walk() call finds the method in the object at once and executes it, not using prototype.

To write such property is nearly the same as to call a function.

That’s why message.fullMessage works in the example below:

let site = {
  siteName: "W3Docs",
  bookName: "Javascript",
  set fullMessage(value) {
    [this.siteName, this.bookName] = value.split(" ");
  },
  get fullMessage() {
    return `Welcome to ${this.siteName}'s ${this.bookName} book`;
  }
};
let message = {
  __proto__: site,
  showMessage: true
};
console.log(message.fullMessage); // Welcome to W3Docs's Javascript book
message.fullMessage = "Welcome to W3Docs's Git book"; // setter triggers

The Value of “this”

It doesn’t matter whether the method is found in the object or its prototype. In a method call, this should also be before the dot. Hence, the setter call message.fullMessage= uses message as this, not site.

It is an essential thing, as, at times, you may have a large object with a lot of methods, and have objects that inherit from it. While the inheriting objects run the inherited methods, they can modify merely their states, but never the state of the large object.

The for ...in loop

The for..in loop also iterates over the inherited properties.

Here is an example:

let animal = {
  speaks: true
};
let dog = {
  runs: true,
  __proto__: animal
};
console.log(Object.keys(dog)); // runs, Object.keys only returns own keys
// for..in loops over both own and inherited keys
for (let prop in dog) console.log(prop); // runs, then speaks

If it’s not what you want, and you intend to exclude the inherited properties, you can use a built-in method obj.hasOwnProperty(key): it may return true in case the obj has a property named key that is not inherited.

You can either filter out the inherited properties or do anything else with them.

For example:

let animal = {
  speaks: true
};
let dog = {
  runs: true,
  __proto__: animal
};
for (let prop in dog) {
  let isOwn = dog.hasOwnProperty(prop);
  if (isOwn) {
    console.log(`Our: ${prop}`); // Our: runs
  } else {
    console.log(`Inherited: ${prop}`); // Inherited: speaks
  }
}

So, the inheritance chain looks like this: the dog inherits from the animal , which inherits from Object.prototype, and then null above it.

So, you may wonder where the method dog.hasOwnProperty comes from. If you look at the chain carefully, you can notice that it is provided by Object.prototype.hasOwnProperty . So, it is inherited.

Also note that all other key/value-getting methods (for example, Object.keys and Object.values) ignore the properties that were inherited. They work on the object itself. But, the prototype properties are not taken into account.




Do you find this helpful?

Related articles