The Prototype Pattern in JavaScript

Christopher T.
November 16th, 2019

There are multiple design patterns that can be implemented in the JavaScript language, and in this post we will be going over the prototype design pattern.

The prototype design pattern is an object-based creational design pattern.

If you need a recap on the three types of design patterns that they are generally coincided with, here is a little overview:

  1. Creational Design Patterns

Instead of you having to directly instantiate objects directly, these are the ones that create them for you. The benefit of this approach is that it gives your program a little more flexibility when deciding which objects need to be created for certain situations.

  1. Behavioral Design Patterns

These patterns are focused on the communication between objects.

  1. Structural Design Patterns

And lastly, these patterns focus on class and object composition. They can be used to compose interfaces through inheritance and define ways to compose multiple objects in order to achieve new functionality.

If this is your first time learning about the prototype pattern, you now might have some idea of what to expect. But if you don't then it is my job to help clear that mystery for you, my friend.

So what exactly is the prototype pattern, and what does it do?

thinking

This pattern's main focus is to help create objects that can be used as blueprints for any object that are created by constructors. It does this through what's called [prototypal inheritance](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritanceandtheprototypechain).

Since JavaScript has native support for prototypal inheritance, it fortunately becomes naturally easy to work with in the language to the point where you don't really need to learn any new concepts but the syntax itself.

With that said, the prototype design pattern is a very useful strategy--which makes it an important and beneficial way to create programs in JavaScript. We will see why in a bit.

When objects are created through the constructor function and contains the name property, then further objects created with the same constructor function will also have this same property as shown below:

function Movie(title) {
  this.title = title
}

const harryPotter = new Movie('Harry Potter')
const rushHour2 = new Movie('Rush Hour 2')
const fastAndFurious = new Movie('Fast And Furious')

console.log(harryPotter.constructor.name)
console.log(rushHour2.constructor.name)
console.log(fastAndFurious.constructor.name)

It sounds like typical class objects, but in reality it avoids using classes altogether. The prototype design pattern simply creates copies of existing functional objects as opposed to defining brand new objects.

The biggest benefit of using the pattern in JavaScript is the performance boost gained as opposed to object oriented classes. This means that when you define functions inside an object, they will be created by reference. In other words, all child objects will point to the same method instead of creating their own individual copies!

Here's a code example of the pattern in action:

const Warrior = function(name) {
  this.name = name
  this.hp = 100
}

Warrior.prototype.bash = function(target) {
  target.hp -= 15
}

Warrior.prototype.omniSlash = function(target) {
  // The target's hp may not be under 50 or this attack will fail on the opponent
  if (target.hp < 50) {
    return
  }
  target.hp -= 50
}

const sam = new Warrior('Sam')
const lenardo = new Warrior('Lenardo')

sam.bash(lenardo)

In our code example, we defined a warrior's attack methods by using Warrior.prototype.<method> = function() {...}. You can see that we instantiated some warriors with the new keyword so now we are looking at two instances. Both instances set their name property according to the name argument that was passed in by the caller.

When we defined the methods bash and omniSlash on the prototype as demonstrated, the two separate instances we're looking at are actually referencing the same bash and omniSlash functions!

const Warrior = function(name) {
  this.name = name
  this.hp = 100
}

Warrior.prototype.bash = function(target) {
  target.hp -= 15
}

Warrior.prototype.omniSlash = function(target) {
  // The target's hp may not be under 50 or this attack will fail on the opponent
  if (target.hp < 50) {
    return
  }
  target.hp -= 50
}

const sam = new Warrior('Sam')
const lenardo = new Warrior('Lenardo')

console.log(sam.bash === lenardo.bash) // true

If we instead defined them like this, then they are not the same, so essentially JavaScript has created another copy of the supposedly same method for each instance:

const Warrior = function(name) {
  this.name = name
  this.hp = 100

  this.bash = function(target) {
    target.hp -= 15
  }

  this.omniSlash = function(target) {
    // The target's hp may not be under 50 or this attack will fail on the opponent
    if (target.hp < 50) {
      return
    }
    target.hp -= 50
  }
}

const sam = new Warrior('Sam')
const lenardo = new Warrior('Lenardo')

console.log(sam.bash === lenardo.bash) // false

So if we didn't use the prototype pattern like the last example, how crazy would it be when we instantiate many instances? We would have cloned methods cluttering up memory that essentially do the same exact thing, who don't even need to be copied unless it relies on state inside instances!

Another variation of extending prototypes is a syntax like below:

const Warrior = function(name) {
  this.name = name
  this.hp = 100
}

Warrior.prototype = {
  bash(target) {
    target.hp -= 15
  },
  omniSlash(target) {
    // The target's hp may not be under 50 or this attack will fail on the opponent
    if (target.hp < 50) {
      return
    }
    target.hp -= 50
  },
}

Which is equivalent to:

const Warrior = function(name) {
  this.name = name
  this.hp = 100
}

Warrior.prototype.bash = function(target) {
  target.hp -= 15
}

Warrior.prototype.omniSlash = function(target) {
  // The target's hp may not be under 50 or this attack will fail on the opponent
  if (target.hp < 50) {
    return
  }
  target.hp -= 50
}

Conclusion

And that concludes the end of this post! I hope you found this to be valuable and look out for more in the future!



© jsmanifest 2019