Javascript Prototypes With Story and Imagination

If you’re learning javascript, there is a critical point on your journey that we all face eventually - your very own ‘red pill or green pill’ moment: do you finally get to the bottom of prototypal inheritance?

Maybe you find yourself asking questions like:

  • Where do all those mysterious array methods, like .find, .forEach etc really come from?
  • Why do I keep seeing stack overflow solutions with things like Array.prototype?
  • So you’re telling me that .prototype and  [[Prototype]] are different things?!

Frustratingly, you may have already googled this dozens of times. But somehow, diagrams explaining the differences between  [[Prototype]], __proto__, and .prototype continue to look like Egyptian hieroglyphs.

Often we talk about abstraction, from a coding perspective. But abstraction can extend beyond your code editor and into your mind. Visual learning techniques translate difficult to comprehend concepts into something our brains can put together.

Even better, with the help of mnemonics and playful analogy,  it can make it memorable. Much about javascript prototypes is to do with the nature of relationships between objects - and what better than to use story and imagination to understand those connections.

Let’s start with the snippet below:

const book = {
  title: 'Visual Learning Techniques',
  wordCount: 3000
}

We tend to think about objects as a kind of vessel - somewhere we can keep values. So we are going to visualise this object as a ship - ships store things, right? We’re avoiding boring explanations, so let’s make this ship a flying ship that sails through the air.

A flying sailing ship, with a captain and crew members.

This analogy becomes problematic when we visualise what values are - islands! Islands are a powerful way to understand the nature and purpose of values. They take up space, and need to be located!

An ocean, with two islands. One island has the label '3000', and the other 'Visual Learning'

Straight away we contradict one common misunderstanding about objects, and it is key to understanding prototypes.

We don’t put the values inside objects. Rather, we use pointers. Using our ship analogy, these are the crew members. Each crew member has a name, eg wordCount

A crew member has one job: shine a spotlight down on a specific value island.

How does he know when to shine his light?  He waits for an instruction from the captain. Every ship has a captain, and the crew members are his ‘properties’. He represents an internal process when an object accesses one of its values. 

A flying object ship shines a spotlight down on the '3000' number island below.

As the developer, when we write code to access an object property, it is the captain’s job to find us a value. He is a determined fellow!

A ship captain.

He will always give us a value back, no matter what. If we asked for a name that does not exist on his ship, he will instruct any one of his crew to shine down on the undefined value - a volcanic, lavary island which is always easy to locate with its large ploom of smoke.

book.nonExistent // undefined

The dangerous crew member

This sequence: asking for an object property, the captain looking for the crew member and if not finding pointing at undefined - is a flawed mental model.

There is an extra step before an expression like  myObj.nonExistent  returns undefined. It’s the key to understanding prototypes. 

When a captain is unable to locate a property, he turns to a very special crew member. Let’s take another look at our object ship.

A flying ship, with a red circle around the crows nest - our [[Prototype]]

At the back of the ship, there’s a crows nest in a structure that looks like [[ ]]. This genie acts as a lookout, ready to his shine his light down upon instruction. 

A screenshot of the console indicating where the Prototype is

This is our mysterious [[Prototype]]. 

Not only is he represented by the [[ ]], but you will also see this property represented with double underscores either side of ‘proto’: __proto__

Both of these point at the same value - they’re the same thing! __proto__ is an accessor (remember object getters?) to the internal [[Prototype]].

This may already be confusing, but stay with me. Don't worry yet about what these two things are pointing at. Just know that both refer to a lonely genie, high up in crows nest on every object ship.

Double square brackets and double underscores? Why the weird and funky syntax? This is to try prevent us, as clumsy developers, from trying to access the value directly like we would any other property. As the MDN docs clearly state:

Warning: Changing the [[Prototype]] of an object is, by the nature of how modern JavaScript engines optimize property accesses, a very slow operation, in every browser and JavaScript engine….  The use of __proto__ is controversial and discouraged.”  

That’s why he is so dangerously high up ( imagine the underscores as a high pole ), in his square box ( picture the weird [[ ]] ), kept completely separate to the other genie properties. Never approach him directly!

The captain asking the Prototype a question.

The question now is, what does this special crew member look for? What value does he find for the captain?

Unlike the other genie properties who might point at any kind of value, the [[Prototype]] only points at other objects. In fact, it only ever shines down on one specific flying object ship.

One ship's Prototype shining down on another flying ship.

When this happens, the captains on each ship are able to communicate with each other. It’s a way of object ships sharing their crew members!

And just like that we have described the foundations of javascript inheritance! If the captain of the first ship is able to access the genie crew members of the other ship, it means objects can “share” properties. This internal mechanism means we can define which methods and properties an object has access to from other objects, simply by setting the [[Prototype]].

Is this sounding familiar? It should, because we javascript developers rely on this feature every day. All those useful array methods, like .find(), .forEach() etc have to come from somewhere! 

Those methods live on another, separate object - in the case of object methods and array methods, that object is the .prototype of the Object constructor function, and the .prototype of the Array constructor function. 

Wait wait wait. The .prototype ? What is this now?  So far we have only been dealing with [[Prototype]], and __proto__, and will get to the troublesome .prototype in a moment.

For now, just understand there are separate objects which all arrays, functions and objects automatically inherit from - a place where our object ship captain can look for any requested crew members, if his own ship doesn’t have them. 

The Prototype Chain

We have already discussed that directly changing the [[Prototype]] is not a good idea. Usually, we usually set it at the time of the object creation, using function constructors, or the Object.create method like below.

Object.create does what it says on the tin - create new objects. But it does one more thing. The argument you supply will be the object that the brand new object's [[Prototype]] will point at.

We are going to create three different objects, to help us visualize a prototype chain. The Object.create will create the new object. The crew member who sits in the crows nest (our [[Prototype]] ) will point his light at the object we supply as the argument to the function.

const citizens = { 
    gary: {
        age: 50,
        profession: 'carpenter'
    }
}

const countries = Object.create(citizens);
const world = Object.create(countries);

console.log(world.gary.profession);

Let’s imagine the sequence of steps for the bottom console.log expression. Notice we are trying to find gary by accessing the world object.

  1. The developer (yes, that’s you), asks for the crew member gary on the world object ship. 
  2. The captain checks his own crew. He checks each crew member, asking ‘ Are you Gary?’ Gary is missing. There is no Gary on his ship.
  3. Don’t despair, the captain knows what to do. He looks up at his [[Prototype]], and yells the name ‘gary’.
  4. The [[Prototype]] shines a light down on the countries ship, and communicates ‘gary’ in morse code
  5. The captain on the countries ship receives the message, and begins to check his own crew.
  6. He too discovers Gary is not on board. 
  7. He asks [[Prototype]] crew member, who finds the citizens ship, and sends the message to find Gary.
  8. The captain on the citizen’s ship receives the message, looks for Gary…and… finds him! What a dramatic climax!
  9. Gary shines down on his value island, and the developer (yes, you), get access to the ‘carpenter’ string value. 

See how much work javascript has to do when you write a simple expression?  Don’t be mean to javascript ever again.

Ships shining their lights at each other, indicating the Prototypal Chain.

The missing piece: the function prototype

Believe it or not, there is yet another ‘prototype’ we need to discuss. We have come across it already - the .prototype, which is how we accessed all the Object and Array methods.

This has everything to do with how objects are made by functions. There’s a special name for those functions: constructors. Here is one represented. Functions are just objects, so they are also a type of ship.

A function ship.

There’s a lot going on in this image -functions are very special indeed. For our purposes, however, we just need to remember they have a special ability to produce new objects - they are the ship builders of the javascript universe, as seen in this snippet:

function BookCreator(title, wordCount){
    this.title = title;
    this.wordCount = wordCount;
}

const book = new BookCreator('Visual Javascript', 100000000000);

Don't be put off by the this variable here. All this is doing is pointing at the new object it is busy creating. This allows us to put stuff inside (like the properties title and wordCount). Although there is no return function, the javascript engine actually returns something for us: a shiny new object ship.

Before we dive further into this code, let’s look a little closer at our function ship analogy. Notice they have their own [[Prototype]], captain and crewmembers. Yes, crewmembers! Function values have their own properties, just like an object. And there is one particular crew member who keeps a low profile. His name is… wait for it… prototype!

The .prototype is just a property, a normal genie crew member, on the function object ship!

We can see him when we log a function to the console.

And here’s the important part: this crew member will point at an empty object ship. You can see that ship circulating at the bottom of the image above. 

Whenever the function constructs a new ship, such as in the example a new book object is made, the [[ Prototype ]] will point at the .prototype of the constructor function 🤯. 

Whooah. If that sounds confusing, go back and look at the images. The [[ Prototype ]] and the .prototype of the constructor function are two completely different objects.

  1. The [[Prototype]] of the constructor function bookCreator will be the object containing all the function methods.
  2. The .prototype of the constructor function is an empty object.
  3. The [[ Prototype ]] of the new ‘book’ object will be the same ship as above.

Challenge: For point number 2, what do you think the [[ Prototype ]] of this empty object would point at?

Why are prototypes so useful? 

The ability for captains to share their crew members, or for objects to look up the keys of other objects, makes them incredibly powerful. 

function BookCreator(title, wordCount){
    this.title = title;
    this.wordCount = wordCount;

    function logTitle(){
        console.log(this.title);
    }

    this.logTitle = logTitle;
}

If you think about it, every time we create a book, we are creating two flying object ships: the object for the book, and the function ship for the .logTitle method. 

That’s a lot of ships, and a lot of memory space. 

Instead, we can make use of prototypes. See the code example below:

function BookCreator(title, wordCount){
    this.title = title;
    this.wordCount = wordCount;
}

function logTitle(){
    console.log(this.title);
}

BookCreator.prototype.logTitle = logTitle;

Now, we are only creating one function ship. We put a crew member called logTitle in the .prototype of the bookCreator constructor function.

No matter how many new books we create, they all have access to the .logTitle method, except this time we didn’t need to make separate function ships for every book! They all share the same one.


function BookCreator(title, wordCount){
    this.title = title;
    this.wordCount = wordCount;
}

function logTitle(){
    console.log(this.title);
}

BookCreator.prototype.logTitle = logTitle;

const book1 = new BookCreator('Visual Javascript', 1000);
const book2 = new BookCreator('Mnemonics', 45500);
const book3 = new BookCreator('Learning Effectively', 14300);

All of these objects inherit the logTitle function because of these labeled steps:

  1. The BookCreator.prototype points at an empty object
  2. In this object we place a crew member called logTitle, who shines his light down at the function ship logTitle.
  3. All three new ships automatically have their [[Prototypes]] pointing at the bookCreator’s .prototype, and have access to the logTitle function.

In this post I have not made use of classes. The good news is there is little to no difference in the sequence of events described above. We use the term ‘syntactic sugar’. Classes provide an easier way to write the code we looked at above. This will be a subject of a future post.

The power of abstraction

If you made it this far, your head is probably bursting with flying ships. Visual abstraction is a powerful means of breaking complex topics down. Imagination and creativity can be included in your learning, and is a proven effective memory technique. 

Did you enjoy this resource? Find it useful? Please let me know below:


© 2023 Code Imagined - The Great Sync. All Rights ReservedView the Terms & ConditionsView the Privacy Policy
kylo@thegreatsync.com