Deep Dive into Prototypal Inheritance in JavaScript

April 24, 2022

Prototype

Objects in JavaScript have an internal property known as the prototype. It is simply a reference to another object and contains common attributes/properties shared across all instances of the object.

  • An object's prototype attribute specifies the object from which it inherits properties.
  • The prototype property is non-enumerable, meaning that it doesn't show up when we try to access the object's properties.

Prototype Chain

When an object gets a request for a property that it does not have, its prototype will be searched for the property, then the prototype's prototype, and so on until an object is reached with null as its prototype. By definition, null has no prototype and acts as the final link in this prototype chain.

Many objects don't directly have Object.prototype as their prototype, but instead have another object that provides a different set of default properties. Functions derive from Function.prototype, and arrays derive from Array.prototype.

In a prototypal system, objects inherit from objects.

Example

const user = { firstName: 'Vishwajeet' } console.log(user.firstName) // Vishwajeet console.log(user.lastName) // undefined console.log(user.toString()) // [object Object]

The toString() result is not undefined but an actual return value. This is because the toString() property lives in Object.prototype, which is connected to this object through the prototype chain.

Setting Prototype

The Object.setPrototypeOf() method sets the prototype of a specified object to another object or null.

const entity = { isHuman: true }; const vishwajeet = { firstName: 'Vishwajeet', lastName: 'Raj' }; Object.setPrototypeOf(vishwajeet, entity); console.log(vishwajeet.firstName) // Vishwajeet console.log(vishwajeet.isHuman) // true

The prototype chain is only searched when the property does not exist on the object.

for-in loop on Objects with Prototype

If you use a for..in loop to iterate over an object, any property that can be reached via its chain and is also enumerable will be enumerated. Use hasOwnProperty to count only the object's own properties:

const person = { name: 'Vishwajeet', lastName: 'Raj' } const extraDetails = { age: 23, eatsApples: true } Object.setPrototypeOf(person, extraDetails) let n = 0; for (let property in person) { if(person.hasOwnProperty(property)) { n++; } } console.log(n) // 2

Prototype delegation with the new keyword

The new keyword does the following things:

  1. Creates a blank, plain JavaScript object.
  2. Adds a property to the new object (__proto__) that links to the constructor function's prototype object.
  3. Binds the newly created object instance as the this context.
  4. Returns this if the function doesn't return an object.
function Laptop(maker) { this.maker = maker this.ram = 4 } Laptop.prototype.ram = 8 Laptop.prototype.color = 'black' const myLaptop = new Laptop('Apple') console.log(myLaptop.ram) // 4 (found on the instance) console.log(myLaptop.color) // black (found on prototype) console.log(myLaptop.__proto__ === Laptop.prototype) // true

What's the difference between __proto__ and prototype?

The difference is that prototype is a property of a class constructor, while __proto__ is a property of a class instance.

Understanding Constructor Property

Every function has the prototype property even if we don't supply it. The default prototype is an object with a single property, constructor, that points back to the function itself.

function func() { console.log('some') } console.log(func.prototype.constructor) // [func function itself] const instance = new func(); console.log(instance.constructor === func) // true

The constructor property will not always point to the function that created it. We can assign the prototype to a new object:

function func() { console.log('some') } func.prototype = {} const instance = new func(); console.log(instance.constructor === func) // false console.log(instance.constructor === Object) // true

Prototype Delegation with class Keyword

The class keyword was introduced with ES6. It's just syntactic sugar over a regular JavaScript function.

class Human {} console.log(typeof Human) // function

Classes in JS use prototypal inheritance.

class Human { isAlive() { return true } } class Doctor extends Human { status() { return this.isAlive(); } } console.log(Human.prototype.isAlive()) // true const doc = new Doctor(); console.log(Object.getPrototypeOf(doc) === Doctor.prototype) // true console.log(Object.getPrototypeOf(Doctor.prototype) === Human.prototype) // true

The extends keyword creates what looks and acts similar to a classical parent-to-child relationship.

With the extends keyword, Human's prototype object is linked to Doctor's prototype object. We're able to access the isAlive function because it actually lives on Human's prototype object, not on the class itself.

GitHub
LinkedIn