Exploring Prototypal Inheritance in JavaScript: Unraveling __proto__

Exploring Prototypal Inheritance in JavaScript: Unraveling __proto__

·

5 min read

Introduction

JavaScript is a versatile programming language known for its unique approach to object-oriented programming through prototypal inheritance. Unlike traditional class-based languages, JavaScript utilizes a prototype-based model that allows objects to inherit properties and behaviors from other objects. This mechanism is at the heart of JavaScript's flexibility and power. In this blog, we'll dive deep into prototypal inheritance, focusing on the mysterious __proto__ property and Object.prototype.

Prototypal Inheritance: A Foundation

The "prototype" is a fundamental concept related to object-oriented programming and inheritance. It refers to an object that serves as a template or blueprint for creating other objects.

  1. Prototype Object: A prototype is like a model that other objects can copy. Imagine it as a blueprint for creating things. In JavaScript, almost everything is built based on these prototypes. They create a chain, where one prototype can be the basis for another, and this can keep going.

For example:

Assign an array of values to the variable arr after it has been declared. We notice something known as a prototype if we attempt to console it.

  1. Prototype Chain: This is like a line of connected prototypes. If something doesn't have a piece of information, it asks the prototype it's connected to. If that prototype doesn't have it, it asks its own prototype, and so on, like a chain of help.

    We can see all the array methods right here inside the prototype. But if we pay closer attention, we can once more see a prototype inside of a prototype, but the inside prototype has methods that are of an object type rather than an array.

    Why is that? Understanding __proto__

__proto__ is a non-standard property in JavaScript that allows access to an object's prototype, enabling inheritance of properties and methods. It's used to query and manipulate the prototype relationship between objects.

Step 1: Exploring Array Prototype

  1. Start by examining Array.prototype. This special blueprint holds methods tailored specifically for arrays, like push(), pop(), and many more.

  2. Printing Array.prototype will display these array-related methods that are available for all arrays.

Step 2: Array and its Prototype Connection

  1. Create an array named arr. This array has a connection to Array's prototype object through a property called __proto__.

  2. Printing arr.__proto__ reveals a connection to the same methods we saw in Array.prototype.

Step 3: The Prototype Chain Mystery

  1. The question arises: If array.__proto__ already holds the array methods from Array.prototype, why is there another __proto__ attached to array.__proto__?

  2. This second __proto__ forms a chain to another prototype object. It's like a bridge to a new set of methods.

Step 4: Journey to Object Prototype

  1. This second __proto__ leads to Object.prototype. This blueprint has methods common to all JavaScript objects, like toString() and hasOwnProperty().

  2. When you look at Object.prototype, you'll recognize the methods that the second __proto__ holds.

Step 5: Expanding the Chain

  1. The second __proto__ in the chain contains methods inherited from Object.prototype.

  2. Wondering what happens if you continue this chain? Imagine adding one more __proto__ after array.__proto__.__proto__.

  3. This additional __proto__ leads nowhere—it points to null.

In Summary:

  1. Array.prototype contains methods tailored for arrays.

  2. array.__proto__ connects to the array methods in Array.prototype.

  3. The second __proto__ chained to array.__proto__ connects to Object.prototype, offering general object methods.

  4. The methods in Object.prototype are recognizable because they match those in the second __proto__.

  5. If you extend the chain with one more __proto__, it will eventually lead to null.

This step-by-step breakdown highlights how the prototype chain works, connecting objects and their methods, ultimately reaching a point where the chain ends.

  1. Modifying Prototypes: It's like adding new features to all things made from a certain template. If you change the prototype, everything that was made from that prototype will have the new feature, but not the ones made before the change.

    Step 1: Establishing Prototype Inheritance

    1. Start by having two objects: obj1 and obj2.

    2. Set up a prototype inheritance link: obj2.__proto__ = obj1.

    3. Now, obj2 inherits properties and methods from obj1.

Step 2: Accessing Inherited Methods

  1. Suppose obj1 has a method called greet().

  2. When you call obj2.greet(), JavaScript checks if greet() exists in obj2.

  3. Since it's not found in obj2, the prototype chain is used to find greet() in obj1.

  4. As a result, obj2 can access and use the greet() method defined in obj1.

Step 3: Sharing Properties Between Objects

  1. Consider that both obj1 and obj2 have a property called name.

  2. When you call obj2.greet(), the greet() method from obj1 is executed.

  3. Inside the method, the name property is fetched from obj2 since the method is executed within the context of obj2.

Step 4: Seeking Properties Through the Chain

  1. Now, imagine obj1 has a unique property called profession.

  2. If you attempt to access obj2.profession, JavaScript starts by looking for profession in obj2.

  3. Since it's not found in obj2, the prototype chain is followed, leading to obj1.

  4. JavaScript finds the profession property in obj1, allowing you to access it via obj2.

  1. Built-in Prototypes: JavaScript has a bunch of pre-made templates. For example, every object you make, whether a regular one, an array, or a function, is based on a common template called Object.prototype. Arrays have some extra stuff that comes from Array.prototype, like special ways to work with lists of things. Functions have their own special things too, from Function.prototype.

Conclusion

Understanding prototypal inheritance is essential for any JavaScript developer to write efficient and maintainable code. It enables the creation of complex data structures and promotes the reuse of code, fostering a deeper understanding of how JavaScript works behind the scenes.