Computer scienceProgramming languagesJavaScriptWorking with dataObjects

Objects Prototyping

13 minutes read

You are already well-acquainted with object properties and methods. Now, it's time to create more flexible and dynamic objects using prototyping. Object prototyping, by definition, is a practice that involves generating reusable object templates known as prototypes. Just like regular objects, a prototype in JavaScript is also an object. In this context, prototypes are like parent objects that keep common things, like properties and methods, to pass them down to their child objects. In other words, the child objects can get these properties and methods from their prototypes and use them. But why do we need such a mechanism?

Understanding object inheritance and prototypes

Imagine you're at a pizza restaurant, with the menu on your table. You find a pizza that suits your taste, but you want to add extra ingredients for more flavor. Would it be easier to describe the desired pizza from scratch to the waiter, or would it be simpler to start with a similar pizza and just add a few extra ingredients? Most likely, the latter option would be easier, right?

In JavaScript, object inheritance works similarly. It allows us to create new objects based on existing objects, just like customizing a pizza with a base and choosing extra ingredients. Object prototyping, on the other hand, is a special mechanism in JavaScript, that enables inheritance by creating a prototype relationship between a parent object and child objects. This relationship gets its name from those parent objects, which act as templates and are called prototypes. Child objects can inherit associated properties and methods from prototypes, while also having their own unique properties.

In this way, object prototyping simplifies the process of creating new objects by offering a base that can be modified according to preferences. Instead of starting from scratch each time, you can reuse existing code and build upon it, much like customizing a pizza with additional toppings.

Object.prototype

In JavaScript, every object comes with default properties, all of which are inherited from a special object named Object.prototype, which is the ultimate parent object for all other objects. In your browser's console, when you enter the object's name, appending a "." (period) as an input, you will see a tab with a lineup of properties and methods on top of the object's own properties. Let's check this on an example object:

const fairytale = {
    title: "Snow White and the Seven Dwarfs"
};

Here we see that the object fairytale has only one property of its own, title. However, when you place a dot notation after fairytale in the console, you'll see something like that:

Properties and methods of the JavaScript object viewed in the browser console: Snow White, title, tab, defineGetter, defineSetter, lookupGetter, lookupSetter, proto, constructor, hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toLocaleString, toString, valueOf

At the top of the list, we see title, which is the own property of the object fairytale. Let's set aside the instances that do not start and end with double underscores for now and focus on the remaining ones in the list, starting with constructor, hasOwnProperty, and the rest. Interestingly, all of these mentioned instances, except for title, are actually part of the Object.prototype in JavaScript and can be accessed by all other objects.

So, if the Object.prototype is the top-level base object, what is its prototype? In case you were wondering, here's a quirky detail: in JavaScript, the prototype of Object.prototype is null.

The "__proto__" property

__proto__is a special property that allows access to and setting of properties of the parent object(s), known as prototype properties. To access the prototype properties of the parent object, you should type the name of the base object as a value next to the __proto__ property.

Let's take a look at how the __proto__ property interacts with other objects in the source code with the following example:

const details = {
  __proto__: fairytale,
  mainCharacter: "princess",
  place: "castle",
  opening() {
    console.log(`In "${this.title}", there was a ${this.mainCharacter} living in a ${this.place}.`);
  }
};

details.opening(); //In "Snow White and the Seven Dwarfs", there was a princess living in a castle.

Here's another object named details. It is evident that details inherits properties from fairytale through the __proto__ declaration. So when we call the opening() method and check the console, we can see that the title property of the fairytale object is successfully accessed and set.

Object.create()

It's important to note that while the __proto__ property is widely supported in JavaScript, it was not standardized until ECMAScript 6 (ES6). In ES6, we have a more recommended and standardized option for establishing inheritance and creating new objects in JavaScript, which is the Object.create() method.

Object.create() is a static method of the object constructor that allows us to create a new object with a specified prototype object and properties. It provides a way to explicitly define the prototype object for the newly created object. This method is considered a more flexible and low-level approach to prototypal inheritance, as it allows direct manipulation of prototypes and objects.

Moving forward, we will use Object.create() to demonstrate examples of prototyping.

And now, here is the refactored code for prototyping the object princess using Object.create():

const details = Object.create(fairytale);
details.mainCharacter = "princess";
details.place = "castle";
details.opening = function() {
  console.log(`In "${this.title}", there was a ${this.mainCharacter} living in a ${this.place}.`);
};

details.opening(); //In "Snow White and the Seven Dwarfs", there was a princess living in a castle.

When using Object.create(), the syntax changes slightly. You provide the prototype object's name as an argument to Object.create(), like Object.create(fairytale) in this case. Then, the properties and methods of the prototype object are assigned individually to the new object using dot notation instead of bracket notation. As in the example above, the name of the details object is accessed using dot notation as with details.mainCharacter and details.place, and the like.

Modifying prototype properties

The values of prototype properties in extended objects can be updated by assigning a new value to that specific property. This new value will override the inherited value from the base object.

const snowWhite = Object.create(details);
snowWhite.name = "Snow White";
snowWhite.enemy = "The Evil Queen";
snowWhite.place = "forest";

snowWhite.keyInfo = function() {
    console.log(`${this.name} was forced to escape into a ${this.place} by ${this.enemy}.`);
};

snowWhite.keyInfo(); // Snow White was forced to escape into a forest by The Evil Queen.

Here, in this part, the assigned value of the place inside snowWhite will override the inherited value of detailsfrom castle to forest.

Optionally, if you want to update prototype properties in child objects created via the __proto__ property, you can modify the value of that specific property inside the extended object. The result will be the same.

const snowWhite = {
  __proto__: details,
  name: "Snow White",
  enemy: "The Evil Queen",
  place: "forest",

  keyInfo() {
    console.log(`${this.name} was forced to escape into a ${this.place} by ${this.enemy}.`);
  }
};

snowWhite.keyInfo(); // Snow White was forced to escape into a forest by The Evil Queen.

Prototype chains

When one prototype object is set as the prototype of another prototype object, a hierarchical inheritance structure is also established across objects. In the following code, snowWhite inherits a property from details along with fairytale. Additionally, details also inherits values from fairytale. This results in a chain of prototype objects starting from the immediate prototypes and extending all the way up the chain. It's worth mentioning that prototyping built-in properties from Object.prototype are also involved in each step of the chain.

const fairytale = {
  title: "Snow White and the Seven Dwarfs"
};

const details = Object.create(fairytale);
details.mainCharacter = "princess";
details.place = "castle";

details.opening = function() {
  console.log(`In "${this.title}", there was a ${this.mainCharacter} living in a ${this.place}.`);
};

const snowWhite = Object.create(details);
snowWhite.name = "Snow White";
snowWhite.enemy = "The Evil Queen";
snowWhite.place = "forest";

snowWhite.keyInfo = function() {
  console.log(`${this.name} was forced to escape into a ${this.place} by ${this.enemy}.`);
};

snowWhite.keyInfo(); // Snow White was forced to escape into a forest by The Evil Queen.

Conclusion

Prototyping is a useful mechanism in JavaScript for creating flexible and reusable object blueprints. Prototypes act as base functions for multiple instance objects. Every JavaScript object has an ancestor object called Object.prototype. Inheritance of properties can be achieved through the __proto__ property, or even better, using Object.create(). Properties can be manipulated by assigning new values to those properties within child objects. By setting one prototype object as the prototype of another, we can construct prototype chains that create a hierarchical inheritance structure among objects.

22 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo