Understanding JavaScript Prototype

Published:

JavaScript is said to be a Prototype-based language, so “prototypes” must be an important concept. Right?

Today I’m going to explain what Prototypes are, what you need to know, and how to use Prototypes effectively.

What are prototypes?

First of all, do not let the word “Prototype” mislead you, unlike a “prototype” in English, the Javascript “prototype” is not a trial version of a new product.

Instead, prototype in JavaScript is simply a word that means absolutely nothing. We can replace prototype with oranges and it can mean the same thing.

For example, think of Apple - before Apple Computers became popular, you would probably think of an Apple as a piece of fruit that grows on trees. “Apple” in Apple Computers doesn’t have a meaning initially – but it does now.

In JavaScript, prototype refers to a system. This system allows you to define properties on objects that can be accessed via the object’s instances.

Prototype is closely related to Object Oriented Programming. It won’t make sense unless you understand what Object Oriented Programming is about.

I suggest you familiarise yourself with this introductory series on Object Oriented Programming before going further.

For example, an Array is a blueprint for array instances. You create an array instance with [] or new Array().

const array = ['one', 'two', 'three']
console.log(array)

// Same result as above
const array = new Array('one', 'two', 'three')

If you console.log this array you won’t see any methods, but you can use methods like concat, slice, filter, and map!

Array doesn't contain method.

Why?

Because these methods are located in the Array’s prototype. You can expand the __proto__ object (Chrome Devtools) or <prototype> object (Firefox Devtools) and you’ll see a list of methods.

Array.prototype contains the methods
Firefox logs prototype as <code>prototype</code>

Both __proto__ in Chrome and <prototype> in Firefox points to the Prototype object, they’re just written differently in different browsers.

When you use map, JavaScript looks for map in the object itself. If map is not found, JavaScript will try to look for a Prototype. If JavaScript finds a prototype, it continues to search for map in that prototype.

So the correct definition for Prototype is: An object that instances can access when they’re trying to look for a property.

Prototype Chains

Here’s what JavaScript does when you access a property:

Step 1: JavaScript checks if the property is available inside the object. If yes, JavaScript uses the property straight away.

Step 2: If the property is NOT inside the object, JavaScript checks if there’s a Prototype available. If there is a Prototype, repeat Step 1 (and check if the property is inside the prototype).

Step 3: If there are no more Prototypes left, and JavaScript cannot find the property, it does the following:

  • Returns undefined (if you tried to access a property).
  • Throws an error (if you tried to call a method).

Diagrammatically, here’s what the process looks like:

Prototype chain.

Prototype Chain example

Let’s say we have a Human class. We also have a Developer Subclass that inherits from Human. Humans have a sayHello method and Developers have a code method.

Here’s the code for Human:

class Human {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastname = lastName
  }

  sayHello () {
    console.log(`Hi, I'm ${this.firstName}`)
  }
}

Human (and Developer below) can be written with Constructor functions. If we use Constructor functions, the prototype becomes clearer, but creating Subclasses becomes harder. That’s why I’m showing an example with Classes. (See this article for the 4 different ways to use Object Oriented Programming).

Here’s how you would write Human if you used a Constructor instead:

function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

Human.prototype.sayHello = function () {
  console.log(`Hi, I'm ${this.firstName}`)
}

Here’s the code for Developer:

class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}

A Developer instance can use both code and sayHello because these methods are located in the instance’s prototype chain.

const zell = new Developer('Zell', 'Liew')
zell.sayHello() // Hi, I'm Zell
zell.code('website') // Zell coded website

If you console.log the instance, you can see the methods in the prototype chain.

`code` and `sayHello` in the prototype chain.

Prototypal Delegation / Prototypal Inheritance

Prototypal Delegation and Prototypal Inheritance mean the same thing.

They’re simply saying we use the prototype system – where we put properties and methods in the prototype object.

Should we use Prototypal Delegation?

Since JavaScript is a Prototype-based language, we should use Prototypal Delegation. Right?

Not really.

I’d argue that it depends on how you write Object Oriented Programming. It makes sense to use Prototypes if you use classes because they’re more convenient.

class Blueprint {
  method1 () {/* ... */}
  method2 () {/* ... */}
  method3 () {/* ... */}
}

But it makes sense NOT to use prototypes if you use Factory functions.

function Blueprint {
  return {
	  method1 () {/* ... */}
	  method2 () {/* ... */}
	  method3 () {/* ... */}
  }
}

Again, read this article for four different ways to write Object Oriented Programming.

Performance Implications

Differences in performance between the two methods do not matter much – unless your app requires millions of operations. In this section, I’m going to share some experiments to prove this point.

Setup

We can use performance.now to log a timestamp before running any operations. After running the operations, we will use performance.now to log the timestamp again.

We’ll then get the difference in timestamps to measure how long the operations took.

const start = performance.now()
// Do stuff
const end = performance.now()

const elapsed = end - start
console.log(elapsed)

I used a perf function to help with my tests:

function perf (message, callback, loops = 1) {
  const startTime = performance.now()
  for (let index = 0; index <= loops; index++) {
    callback()
  }
  const elapsed = performance.now() - startTime
  console.log(message + ':', elapsed)
}

Note: You can learn more about performance.now in this article.

Experiment #1: Using Prototypes vs Not using Prototypes

First, I tested how long it takes to access a method via a prototype vs another method that is located in the object itself.

Here’s the code:

class Blueprint () {
  constructor () {
    this.inObject = function () { return 1 + 1 }
  }

  inPrototype () { return 1 + 1 }
}

const count = 1000000
const instance = new Blueprint()
perf('In Object', _ => { instance.inObject() }, count)
perf('In Prototype', _ => { instance.inPrototype() }, count)

The average results are summarised in this table as follows:

Test1,000,000 ops10,000,000 ops
In Object3ms15ms
In Prototype2ms12ms

Note: Results are from Firefox’s Devtools. Read this to understand why I’m only benchmarking with Firefox.

The verdict: It doesn’t matter whether you use Prototypes or not. It’s not going to make a difference unless you run > 1 million operations.

Experiment #2: Classes vs Factory Functions

I had to run this test since I recommend using Prototypes when you use Classes, and not using prototypes when you use Factory functions.

I needed to test whether creating Factory functions was significantly slower than creating classes.

Here’s the code.

// Class blueprint
class HumanClass {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.lg(`Hi, I'm ${this.firstName}}`)
  }
}

// Factory blueprint
function HumanFactory (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
	    console.log(`Hi, I'm ${this.firstName}}`)
	  }
  }
}

// Tests
const count = 1000000
perf('Class', _ => { new HumanClass('Zell', 'Liew') }, count)
perf('Factory', _ => { HumanFactory('Zell', 'Liew') }, count)

The average results are summarised in the table as follows:

Test1,000,000 ops10,000,000 ops
Class5ms18ms
Factory6ms18ms

The verdict: It doesn’t matter whether you use Class or Factory functions. It’s not going to make a difference even if you do run > 1 million operations.

Conclusion about performance tests

You can use Classes or Factory functions, and you can choose to use Prototypes, or you can choose not to. It’s really up to you.

There’s no need to worry about performance.

Here's how to become great at JavaScript in less than 2 months

If you’re stuck because of your lack of JavaScript skills, you can stop worrying now.

We’ll help you become amazing at JavaScript — we have a course that can help you understand and use JavaScript easily.

And we’re going to give you three chapters for free because we love to help developers become superheroes.

Just click the button below to become an amazing JavaScript developer.