const - JS | lectureJavaScript OOP

Prototypal Inheritance on Built-In Objects

JavaScript OOP

In this lecture, we will have a look at prototypal inheritance and the prototypal chain on built-in objects such as arrays. But before we do that, let's start by inspecting some of the stuff that we just talked about in the previous lecture. We will keep using the evan object as an example. Let's get to it.

Let's start by taking a look at evan.__proto__. And we already know it is the prototype of evan, which is exactly the prototype property of Person.

index.js
console.log(evan.__proto__);

evan.proto

But now, let's actually move up in the prototype chain, and essentially take a look at the prototype of evan's prototype.

index.js
console.log(evan.__proto__.__proto__);

Do you remember what that's going to be from the previous lecture? Well, it is the prototype property of Object.

evan.proto.proto

That's why you can see that the value of the constructor property is Object. And then next to it, you see the hasOwnProperty method that we talked about in the previous lecture. Hence, this is the reason why we were able to do evan.hasOwnProperty('firstName').

Next, let's take this one step further and take a look at the prototype of the prototype of the prototype 😅.

index.js
console.log(evan.__proto__.__proto__.__proto__);

Do you remember what this one is? Well, it is null. And that's because Object.prototype is usually the top of the prototype chain.

Let's also have a look at the strange constructor property that we also talked about in the previous lecture.

index.js
console.log(Person.prototype.constructor);

Just as we learned in the previous lecture, it will point back to the Person function itself.

Person.prototype.constructor

To inspect the function, use the console.dir method.

index.js
console.dir(Person.prototype.constructor);

Person.prototype.constructor

Let's now talk about prototypes in arrays. Let's start by creating an array.

index.js
const arr = [1, 6, 3];

Let's now take a look at the prototype of the array.

index.js
console.log(arr.__proto__);

arr.proto

If you check your console, and from the above image, you can see we have a list of methods, some of which we have already seen. For example, includes, filter, find, and every. And so this is the reason why all the arrays get access to these methods. So each array does, of course, not contain all of these methods. Instead, each array will inherit these methods from its prototype.

And we can also check that arr.__proto__ is the prototype of Array.

index.js
console.log(arr.__proto__ === Array.prototype); // true

Just like objects, using the shorthand ([]) to create an array is the same as using the new keyword to create an array.

index.js
// new Array === []

Let's move one step further in the prototype chain and see what we get.

index.js
console.log(arr.__proto__.__proto__);

arr.proto.proto

As expected, we get the prototype of Object. You will notice that if you check the documentation of the find method, for example, on MDN, you will see that the name of the method is Array.prototype.find. And that's because the find method lives in the prototype property of the array constructor function. One more time, you can see that prototypal inheritance is really a mechanism for reusing code. So all of those built-in methods have to exist only once somewhere in the JavaScript engine, and then all the arrays in our code get access to them through the prototype chain and prototypal inheritance.

Let's now move another step further. At this point, we know that any array inherits all its methods from its prototype. Hence we can use that knowledge to extend the functionality of arrays even further. All we will have to do is to add a new method to the prototype property of the array constructor, and all the arrays will then inherit it.

Let's say we want to add a new method to the array prototype that returns all the unique elements of an array.

index.js
Array.prototype.unique = function () {
  return [...new Set(this)];
};

Let's now use this new method on our array.

index.js
const arr = [1, 6, 1, 4, 5, 2, 9, 7, 9, 6, 5, 3];
console.log(arr.unique()); // ==> [ 1, 6, 4, 5, 2, 9, 7, 3 ]

However, what we just did here, i.e., extending the prototype of a built-in object, is generally not a good idea. What I mean is, if you're working on a small project on your own, then that would be fine. But really, don't get into the habit of doing this for two main reasons:

  1. The first reason is that the next version of JavaScript might introduce a method with the same name as the one you just added but it might do something completely different. And so your code will then use that new method, which, remember, works differently, and then that will probably break your code.
  2. The second reason is that when you work on a team of developers, then this is really going to be a very bad idea because if multiple developers implement the same method with a different name, then that's just going to create so many bugs.

It's fun implementing it 🤩 but in practice, you should probably not do it 🛑.