Object Prototypes

Open the project folder

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

07.Objects-In-More-Depth > 03.Object-prototypes

Constructor function recap

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.

Viewing the prototype contents

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.

The 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.

Adding to the prototype

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

Accessing custom prototype properties

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

Recap

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.