In our starter files, open the index.html page from this lessons folder:
07.Objects-In-More-Depth > 03.Object-prototypes
If you did not quite understand the role of the prototype earlier, hopefully now seeing it in action with our own objects will help.
In this starter project, we begin with the same example as previously:
function User(first, last, occupation, lives) {
this.firstName = first;
this.lastName = last;
this.occupation = occupation;
this.lives = lives;
this.fullName = function () {
return this.firstName + ' ' + this.lastName;
};
}
const chris = new User('Chris', 'Dixon', 'Dev', 'UK');
const homer = new User(
'Homer',
'Simpson',
'Safety Inspector',
'Springfield'
);
console.log(homer);
This has a constructor function to create templates or blueprints. This means we can create multiple objects with the same properties and methods available. We then looked at how to add properties and methods to both a single object, and to the function so they apply to all new objects created.
Along with adding to the constructor function, we can also add directly to the prototype. Before we do this, first let’s refresh what we know about the prototype.
At the bottom of the script we are logging our homer
object:
console.log(homer);
In the console you will see something like this:
User {firstName: 'Homer', lastName: 'Simpson', occupation: 'Safety Inspector', lives: 'Springfield', fullName: ƒ}
firstName: "Homer"
fullName: ƒ ()
lastName: "Simpson"
lives: "Springfield"
occupation: "Safety Inspector"
[[Prototype]]: Object
It contains our properties, methods, and a Prototype
section at the end. This is a link to the prototype chain, open this up to see the contents:
[[Prototype]]: Object
constructor: ƒ User(first, last, occupation, lives)
[[Prototype]]: Object
Inside we see our own constructor (User
). This was used to create this object, and a second [[Prototype]]
object:
// ...
[[Prototype]]: Object
constructor: ƒ User(first, last, occupation, lives)
[[Prototype]]: Object
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
This contains many things which we did not add to our own object. Remember from earlier, JavaScript objects inherit properties and methods from parent objects.
Object()
At the top of this inheritance chain is the Object()
, let’s log this for reference:
console.log(Object()); // {}
This will show an empty object in the console. It is empty in terms of having no properties or methods, but the purpose of this top-level object is to contain the bare minimum properties and methods to pass to other objects through the prototype chain.
And we can see this if we open it up:
{}
[[Prototype]]: Object
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
Notice the contents is the same as inside our homer
object Prototype
. Any object we create, such as homer
will inherit all of this through the prototype chain.
As we looked at earlier, this is why things like custom arrays have methods available such as forEach
, push
and pop
. We don’t create these methods ourselves, but instead they are inherited from the prototype so all array's we make can use them.
We should not add our own properties and methods to objects we have not created ourselves. Instead, we can add them to our own custom constructor, such as our User
.
We do this by accessing a prototype
property on our User
:
function User(first, last, occupation, lives) {
this.firstName = first;
this.lastName = last;
this.occupation = occupation;
this.lives = lives;
this.fullName = function () {
return this.firstName + ' ' + this.lastName;
};
}
User.prototype.cool = true;
Again log one of our objects to the console. Now when we open up the Prototype
object, alongside our constructor is the cool
property we set:
User {firstName: 'Homer', lastName: 'Simpson', occupation: 'Safety Inspector', lives: 'Springfield', fullName: ƒ}
firstName: "Homer"
fullName: ƒ ()
lastName: "Simpson"
lives: "Springfield"
occupation: "Safety Inspector"
[[Prototype]]: Object
cool: true
constructor: ƒ User(first, last, occupation, lives)
[[Prototype]]: Object
We see cool
is not placed on the original object alongside firstName
etc. But, if we try to access it, cool
will then be looked up on the prototype chain. If it was not on our own prototype, it would then look further up the chain.
console.log(homer.cool); // true
The same goes for methods, we can add these to the prototype:
function User(first, last, occupation, lives) {
this.firstName = first;
this.lastName = last;
this.occupation = occupation;
this.lives = lives;
// this.fullName = function () {
// return this.firstName + ' ' + this.lastName;
// };
}
User.prototype.cool = true;
User.prototype.fullName = function () {
return this.firstName + ' ' + this.lastName;
};
And we can see this by calling the method:
console.log(homer.fullName()); // Homer Simpson
The prototype may seem complex to begin, but it is just a way to inherit properties and methods form a parent object. Either a parent we have created, or from the top-level Object
which JavaScript provides.
The prototype is a property on an object that we make available to be inherited by others. An example of this was our cool
and our fullName
additions we added to the prototype. These have now been made available for other objects to inherit. Including any objects we create from this constructor function.