DEV Community

Cover image for Prototypical Inheritance
Parwinder 👨🏻‍💻
Parwinder 👨🏻‍💻

Posted on

Prototypical Inheritance

In the very beginning on this blog, we talked about how everything in JavaScript is an object. When we create objects, we have the inherent need for reusing its properties or methods. Most modern languages support inheritance in one way or the other. JavaScript does so by using a prototypical chain or inheritance.

Every object in JavaScript has a hidden property called [[Prototype]]. It has one of two values: either null (marking the end of the prototypical chain) or a reference to another object.

The prototype object has a prototype of its own, and so on until an object is reached with null as its prototype.

null has no prototype and acts as the end of the prototypical chain, as I mentioned above.

Inheriting properties from objects

// declare an initial object animal with height and weight property
let animal = {
    height: 5,
    weight: 50
};

console.log(animal.__proto__); // null or  {}
// since animal is not inherited from anything, it doesn't have a prototypical chain

// create an object fish from animal
let fish = Object.create(animal);

console.log(fish.height); // 5, inherited from animal
console.log(fish.weight); // 50, inherited from animal
console.log(fish.__proto__); // { height: 5, weight: 50 }
// ^ chain points to animal, that is how we got fish height and weight

fish.canSwim = true; // adding a property to fish object

console.log(animal.canSwim); // undefined, it does not exist on animal. It is fish's own property
console.log(fish.canSwim); // true

let octopus = Object.create(fish); // create an object from fish

console.log(octopus.height); // 5, traversing the prototype chain octopus => fish => animal
console.log(octopus.weight); // 50, inherited all the way from animal
console.log(octopus.__proto__); // { canSwim: true }, points to fish but only shows properties that fish "owns"

octopus.legs = 8; // adding a property to octopus object

console.log(octopus.legs); // 8
console.log(animal.legs); // neither animal or fish has the legs property
console.log(fish.legs);

// hasOwnProperty method is true when an Object owns a property and did not inherit it
console.log(octopus.hasOwnProperty("legs")); // true
console.log(octopus.hasOwnProperty("height")); // false
console.log(fish.hasOwnProperty("weight")); // false

__proto__

In the above examples, we used __proto__ to access the prototype of an object. __proto__ is the getter and setter for [[Prototype]]. We have newer methods to do it now (getPrototypeOf or setPrototypeOf), but __proto__ is supported by most (browsers or server-side).

There are only two rules for __proto__:

  1. At no point can a __proto__ create a circular reference or dependency. JavaScript throws an error if we assign proto in a circular reference.
  2. As I mentioned before the value of proto can be either an object or null only.

Inheriting properties using a constructor

let foo = function() {
    this.name = "Parwinder";
    this.age = 57;
}

let bar = new foo(); // create an object bar using function foo

console.log(bar); // foo { name: 'Parwinder', age: 57 }, due to inheritance
console.log(bar.name); // Parwinder, inherited from foo

foo.prototype.car = "Volvo"; // adding a new property "car" to original function foo

console.log(bar.car); // Volvo
// check bar if it has a property car, if not follow up the prototype chain.
// get to foo following the chain
// does foo have car on its prototype? Yes. Log the value "Volvo"

console.log(bar.gender); // undefined
// check bar if it has a property gender, if not follow up the prototype chain.
// get to foo following the chain
// does foo have gender on its prototype? no.
// go up the prototypical chain.
// we have reached the end of the chain with null. Log undefined.

Behavior of this keyword and inheritance

No matter if a method is found in an object or its prototype, this always refers to the object before the dot. Let's understand with an example.

const checkVotingRights = {
  age: 24,
  legalToVote: function() {
    return this.age > 18;
  }
};

console.log(checkVotingRights.age); // 24
// When calling checkVotingRights.age in this case, "this" refers to checkVotingRights
console.log(checkVotingRights.legalToVote());

const teenagerEligible = Object.create(checkVotingRights);
// teenagerEligible is an object that inherits checkVotingRights

teenagerEligible.age = 13; // set age on the newly created object

console.log(teenagerEligible.legalToVote()); // false
// when teenagerEligible.legalToVote is called, "this" refers to teenagerEligible

Using delete Operator with Object.create

Whenever we delete a key from an object and that deleted key was inherited, logging the key will log the inherited value.

var a = {
    a: 1
};

var b = Object.create(a);

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

b.a = 10;

console.log(a.a); // 1
console.log(b.a); // 10

delete b.a;

console.log(a.a); // 1
console.log(b.a); // 1, value deleted but showing value from a

delete a.a;

console.log(a.a); // undefined
console.log(b.a); // undefined

Gotcha with for..in loop

for..in loop iterates over properties of an object but it loops over inherited properties as well!

let animal = {
    height: 5,
    weight: 50
};

let fish = Object.create(animal);

fish.canSwim = true;

for (let item in fish) {
    console.log(item); // canSwim, height, weight
}

for (let item in fish) {
    console.log(fish[item]); // true, 5, 50
}

Top comments (4)

Collapse
 
eliowar28 profile image
Eliomar Garzon

Thanks for this great article. I love JavaScript and prototypal inheritance always seems so hard to me to catch. Idk if it's I haven't build something using it for real.

Collapse
 
louislow profile image
Louis Low

Beautiful article! Worth keeping.

Collapse
 
bhagatparwinder profile image
Parwinder 👨🏻‍💻

Thanks!♥️

Collapse
 
bias profile image
Tobias Nickel

Thanks for sharing this, without the new class syntax sugar.