Prototypes
We talked about prototypes, prototypal inheritance, and delegation in the previous lectures. But how does all of this work in JavaScript? Well, before we get into the details, we can summarize the concept of prototypes in JavaScript as follows:
Person.prototype
from the previous lecture)Let's now actually add a method to the prototype property of the Person
constructor function. The prototype property is an object, so we can write:
Person.prototype.calculateAge = function () {
console.log(2024 - this.birthYear);
};
console.log(Person.prototype);
So, based on what we said earlier, which is that every object that's created by the Person
constructor function will get access to all the methods defined on the prototype property of that constructor function, we should be able to call the calculateAge
method on the evan
object, right?
evan.calculateAge();
It works! So, we can now use the calculateAge
method on the evan
object even though we didn't define it on the object itself.
From above, we can see that our evan
object doesn't have a calculateAge
method. But still, it has access to it because of prototypal inheritance. Beautiful! So, this solution effectively solves the problem we had when we added the calculateAge
method directly to each of the objects.
// Previous Lecture - don't do this!
const Person = function (firstName, lastName, birthYear, profession) {
// Instance properties
this.firstName = firstName;
//...
this.calculateAge = function () {
console.log(2024 - this.birthYear);
};
};
Once again, with this above solution from the previous lecture, we would have created a copy of the calculateAge
method and attached it to each and every object that's created by the Person
constructor function. And so that's why we don't do that. Instead, we add the method directly to the prototype property of the constructor function so that it's only created once and then shared across all the objects that are created by that constructor function.
In a nutshell, this is how we implement very basic prototypal inheritance in JavaScript. So, we just observed that the evan
object is kind of somehow connected to the Person
constructor function. That's why it can have access to the calculateAge
method located inside the prototype property of the Person
constructor function. But how and why does this work? Well, it works because any object always has access to the methods and properties from its prototype. And the prototype of evan
is Person.prototype
.
We can actually confirm that because each object has a special property called __proto__
.
console.log(evan.__proto__);
From the result we can clearly see the calculateAge
method, and that's the reason why evan
is able to use it. So, the prototype of the evan
object is basically the prototype property of the constructor function.
console.log(evan.__proto__ === Person.prototype);
However, there's one more thing we need to understand. The prototype property of the constructor function is not the prototype of the constructor function itself. It's the prototype of all the objects that are created by the constructor function. It is a subtle but important distinction.
Let's use another built in method to prove this.
console.log(Person.prototype.isPrototypeOf(evan));
console.log(Person.prototype.isPrototypeOf(Person));
The next question you are surely asking yourself is: "Where does the __proto__
property on the evan
object come from?" Well, do you remember what step number three does when we use the new
operator? It links the newly created empty object to the prototype. And so basically, it is at that moment that the __proto__
property is created. That is, it creates the __proto__
property and links it to the prototype property of the constructor function that is being called.
We can also set properties on the prototype property of the constructor function.
Person.prototype.species = 'Homo Sapiens';
This property will be available on all the objects that are created by the Person
constructor function.
console.log(evan);
console.log(evan.species); // Homo Sapiens
We can see that this property is not directly on the evan
object. So, it's not its own property. Own properties are only the ones that are declared directly on the object itself, not including inherited properties. There exist a method called hasOwnProperty
that we can use to check if a property is an own property or not.
evan.hasOwnProperty('firstName'); // true
evan.hasOwnProperty('species'); // false