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:
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.
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!
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.
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!
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
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.
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.
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 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.
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.
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.
See how much work javascript has to do when you write a simple expression? Don’t be mean to javascript ever again.
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.
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.
Challenge: For point number 2, what do you think the [[ Prototype ]] of this empty object would point at?
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:
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.
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: