A Guide to JavaScript Prototypal Inheritance

A Guide to JavaScript Prototypal Inheritance

TL;DR

At core, Prototypical inheritance in JavaScript is a way for an object to inherit the properties and methods from other object. In JavaScript object has a special and hidden property [[Prototype]] This property can either be null or reference to other object, called "a prototype". This property can be accessed via __proto__ or Object.getPrototypeOf(). When we read a property from an object and if it's missing in that object, JavaScript reaches out to it's prototype object and tries to find it there, This is what prototypical inheritance is.

const person = {
     "department": "IT",
     "location": "San Jose"
}
const employee = {
    "name": "Shubham"
}

Object.setPrototypeOf(employee, person); // sets employee.[[Prototype]] = person

You can also use __proto__ to set or get the prototype of an object. As is generally considered the proper way to set the prototype of an object. You should always use it in favour of the deprecated Object.prototype.__proto__ accessor.

Now, if we read the property from employee and it's missing, JavaScript engine will try to read it from it's prototype which is person.

For instance:

alert(employee.department); //IT
alert(employee.location); //San Jose

JavaScript Prototypical Inheritance

Prototypical inheritance is a feature in JavaScript to create objects which can inherit properties or methods from other objects, Instead of class based inheritance, JavaScript uses a prototype based model.

Key Features:

In JavaScript, every object has a prototype. If you create an object using either object literal syntax or object constructor, The new object is linked with object prototype of the object constructor or Object.prototype if nothing was specified. We can reference the object prototype using either __proto__ or [[Prototype]]. It's recommended to use built-in method to get/set the prototype of an object. You can use Object.getPrototypeOf(obj, prototype) and Object.setPrototypeOf() to get/set prototype of an object.

Please note, for historical reasons, you can also use __proto__ as a getter/setter for prototype object but it's not recommended because it's outdated and deprecated in browser environments.

// Define a constructor function
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// Add a method to the prototype
Person.prototype.sayHello = function () {
  console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};

// Create a new object using the constructor function
let john = new Person('John', 30);

// The new object has access to the methods defined on the prototype
john.sayHello(); // "Hello, my name is John and I am 30 years old."

// The prototype of the new object is the prototype of the constructor function
console.log(john.__proto__ === Person.prototype); // true

// You can also get the prototype using Object.getPrototypeOf()
console.log(Object.getPrototypeOf(john) === Person.prototype); // true

// You can set the prototype of an object using Object.setPrototypeOf()
let newProto = {
  sayGoodbye: function () {
    console.log(`Goodbye, my name is ${this.name}`);
  },
};

Object.setPrototypeOf(john, newProto);

// Now john has access to the methods defined on the new prototype
john.sayGoodbye(); // "Goodbye, my name is John"

// But no longer has access to the methods defined on the old prototype
console.log(john.sayHello); // undefined

A prototype of an object can either be an another object or null. Any other type is ignored by JavaScript engine.

Prototype chain:

When we access a property or a method on an object, JavaScript tries to find it on the same object if it can't then it tries to find it in object's prototype and if still cannot find it, it will look into object prototype's prototype, and so on, until it cannot finds it in the chain or reaches at the end of it (i.e. null).

Creating a prototype:

  1. Object Constructor: When a function is used as a constructor with new keyword, the new object's prototype is set to the constructor's prototype property.

     function Circle(radius) {
         this.radius = radius;
     }
    
     Circle.prototype.getArea = function() {
         return Math.PI * Math.pow(this.radius, 2);
     };
    
     const circle = new Circle(5);
     circle.getArea();   // `78.53981633974483`
    
     // The constructor function Circle's prototype is assigned to
     // circle object.
     console.log(Object.getPrototypeOf(circle) === Circle.prototype); // true
    
  2. Object.create(): This method provides a way to create an object with given prototype and properties. This also allow you to create an orphan objects (no pun intended) without any prototype assigned using, Object.create(null). This should return the literal empty object with prototype set to null. This method provides a straight forward way to setup a prototypical inheritance.

     const car = {
       brand: "BMW",
       year: 2024,
       FWD: "yes"
     }
    
     const obj = Object.create(null);
     console.log(Object.getPrototypeOf(obj)); // null
    
     const X1 = Object.create(car);
    
     X1.model = 'BMW X1 series'; // Property model is set on X1 not car.
     X1.color = "Black";
    
     console.log(X1.brand); //BMW
    

That's a wrap! Hope you have enjoyed learning these concepts as much as I did while writing. Do follow or subscribe to my newsletter for more content like this.