Inheriting Object Prototypes

Open the project folder

In our starter files, open the index.html page from this lessons folder:

07.Objects-In-More-Depth > 04.Inheriting-object-prototypes

Limitations for our current example

We have discovered there is a parent JavaScript Object which has a prototype. And how when we create new objects, they automatically inherit certain things through the prototype chain.

But what if we wanted to also allow our own objects to be inherited from too?

In the starter files we have a User object, and we may begin to realise that this object has some limitations for our use:

function User(first, last, occupation, lives) {
  this.firstName = first;
  this.lastName = last;
  this.occupation = occupation;
  this.lives = lives;
}

const chris = new User('Chris', 'Dixon', 'Dev', 'UK');
const homer = new User(
  'Homer',
  'Simpson',
  'Safety Inspector',
  'Springfield'
);
console.log(homer);

Considering the above example, one could be a real person, and one a fictional charater from a TV series. Real and fictional people may need different properties.

We may for example want to add the name of the show which the fictional character was from, but this would not apply to the real person, and this now makes it difficult to expand this object much further.

What we can do is have the User object as a base, including only the properties which both the real and the fictional users share.

Then we can create two more objects with the specific properties we need. Both will inherit from the original User, so we still have access to things like the name and occupation.

The Object.create() method

To do this, we have an object method called create.

Just before we add this to our current example, let’s look at how we can do this with a regular, standalone object. First, add any object to the bottom of our script:

let product1 = {
  title: "an amazing blue shirt",
  description: "blue shirt",
};

Notice here this title and description are generic, and could apply to multiple blue shirts, and we could just change some details such as the brand or size.

Then create our second product using an Object method called create():

let product1 = {
  title: "an amazing blue shirt",
  description: "blue shirt",
};

let product2 = Object.create(product1);
console.log(product2);

We pass in product1 to the Object.create() method, and store this into a new variable called object2. The console will have the following output from product2:

{}
  [[Prototype]]: Object
  description: "blue shirt"
  title: "an amazing blue shirt"
  [[Prototype]]: Object

Initially we see an empty object for product2, but if we open the Prototype we have access to the title and description. And this is how the create() method works. It creates a new object, and uses the existing object we pass in as the prototype.

As we discovered in the previous lesson, notice how we have multiple Prototype properties since this is a chain. We have our own first, then last, we have the ones we inherit at the top of the chain from Object.

As we have already learned, even though our own object is empty, we can access these inherited properties from product1 by their name:

console.log(product2.title);

We can also add our own properties and methods as you would expect:

let product2 = Object.create(product1);
product2.size = 'small';
console.log(product2);

We can now remove this small example and go back to our original starter code:

function User(first, last, occupation, lives) {
  this.firstName = first;
  this.lastName = last;
  this.occupation = occupation;
  this.lives = lives;
}

const chris = new User("Chris", "Dixon", "Dev", "UK");
const homer = new User(
  "Homer",
  "Simpson",
  "Safety Inspector",
  "Springfield"
);

This is a little different since we have a constructor function rather than a single object. If we do the same as before like this:

let copy = Object.create(User);
console.log(copy);

This will make a copy of the function which is not what we want.

Inheriting from constructor functions

What we are looking for is a way to create more constructor functions which inherit the properties of User, let’s start with the Character:

function User(first, last, occupation, lives) {
  this.firstName = first;
  this.lastName = last;
  this.occupation = occupation;
  this.lives = lives;
}

function Character() {}

Then we also need to pass in any extra information we need for this type of object, such as the show they are from:

function Character(show) {
  this.show = show;
}

When we create a new Character object, we not only want the show property, but also the firstName, lastName, occupation and lives properties from the User.

The call() method

For this, we have a method available on the User function called call():

function Character(show) {
  User.call();
  this.show = show;
}

The call() method is available on a functions prototype. It allows for a function belonging to one object to be called inside of a different object. Meaning we can call our User function from inside this Character function.

The call method is not exclusive to this use case, it can be used anytime we want to access a function, or a method located on another object.

The User function also takes in these parameters:

function User(first, last, occupation, lives)

We also need to copy these over to our Character function where we call it:

function Character(first, last, occupation, lives, show) {
  // Add user properties
  User.call(first, last, occupation, lives);
  this.show = show;
}

Using the Character function

Then modify our homer variable to use this new Character constructor, rather than User. Also pass in the value of the show the character is from:

// 1. Use Character function rather than User
const homer = new Character(
  'Homer',
  'Simpson',
  'Safety Inspector',
  'Springfield',
  // 2. also add in the shows value:
  'The Simpsons'
);

console.log(homer); // 3. log to the console

And the result in the console:

Character {show: 'The Simpsons'}
  show: "The Simpsons"
  [[Prototype]]: Object
    constructor: ƒ Character(first, last, occupation, lives, show)
    [[Prototype]]: Object

This shows homer is a Character object, and also has the show property, meaning homer.show will now work:

console.log(homer.show); // The Simpsons

Inheriting properties

What about the inherited properties?

console.log(homer.occupation); //undefined

The inherited properties don’t initiall work. This is because we are missing one small detail when calling our User function to get these values. And that is the first parameter of this:

function Character(first, last, occupation, lives, show) {
  // Add this as parameter
  User.call(this, first, last, occupation, lives);
  this.show = show;
}

As mentioned earlier the this keyword can be a complex thing to understand. Here we are saying when running our function, we can access the values using this, just as we are above in the User function:

function User(first, last, occupation, lives) {
  this.firstName = first;
  this.lastName = last;
  this.occupation = occupation;
  this.lives = lives;
}

Now the inherited properties should work:

console.log(homer.occupation); // Safety Inspector

The instanceof operator

If we want to check which one of our constructors created this object, we can use instanceof:

console.log(homer instanceof Character); // true

console.log(homer instanceof User); // false

And this is how JavaScript works using objects, there are objects right from the beginning that we can inherit from that we may be unaware of.

But as we dig deeper like this, we begin to understand this is so each new object, and this refers to object types such as arrays and functions too, can have a set of helpful properties and methods we can use each time we create our own objects.

And as we just discovered, we can also take advantage of this prototype inheritance for our own objects if needed.