If I were to try sum up javascript, I would describe it as a giant interconnected web of pointers.
In the Great Sync we have discussed a few ways of pointing already:
The Great Sync genies find those values for us. But so far we have only been talking about primitive values - the ‘natural’ islands, with fixed coordinates in the program’s memory.
These are the values that can be passed around your program. For example, you can pass a number value into a function execution. Or you can pass string values into an array. The values are the islands themselves. This is what we mean by Pass By Value.
const num = 2;
getSquareRoot(num)
Num is passed into the function getSquareRoot. Num, the expression, is the genie locating the number 2. You could just as easily replace num with the value, like this:
getSquareRoot(2);
In the example above, we pass in the value 'Simon' to the function. We are passing in the actual value - a string primitive.
But what about objects, arrays and functions? In our code, we are unable to deal with these structures directly. The flying ships of the javascript world live in an invisible dimension - The Heap Multiverse
According to our mental model so far, the code below is impossible. The genies can’t point at something that’s invisible. In that case, we can’t pass them around as values.
const obj = { }; // { } is invisible
console.log(obj)
And yet, we also know that there is nothing wrong with the code above. So what’s missing?
Introducing javascript references.
They are simply links to objects, as described in the MDN docs. In The Great Sync, we can think of them as the link between the world of primitives, and the The Heap Multiverse where our objects live.
These links are a special kind of island.
Every object (including arrays and functions) get their own. They live in the ocean of our code, together with the primitives. This means genie expressions can find them!
Unlike primitives, however, they serve only one purpose: as pointers to objects. They are not the values themselves.
They are equipped with a special telescope🔭 which can see through The Heap Multiverse, and pinpoint the invisible object ship. This is how we know where our objects are!
So obj = { }, really is a genie called obj, sitting on a loo on a rock island, looking through a telescope at an object ship flying through the Heap Multiverse.
Let's take a look at an example of a function which receives an object and a string primitive.
const user = { name: 'Thili' }
const newName = 'Simon';
function updateUserName(userObject, nameString){
userObject.name = nameString;
}
updateUserName(user, newName) // Passing in the string value 'Simon'
User is a genie sitting on a reference island, pointing at a flying ship. newName is a genie sitting on a rainy string island. We pass both these values into the function updateUserName.
Now, what if we tried to swap the variables out for the actual values?
// Instead of this
updateUserName(user, newName)
// Let's try use the actual value
updateUserName({ name: 'Thili' }, 'Simon')
If we try console.log(user), the original object which the variable user is assigned to - do you think there will be any change?
No. The name is still 'Thili'. As soon as we tried to type the object directly into the function, we were assuming we were passing in the value itself.
In fact, all we did was create a NEW object, with the identical contents, and we passed in a reference to the new object. The original user is unaffected.
The above examples may seem obvious to you. But where reference values really start to cause problems is when we confuse which objects our genies are locating through their telescopes!
It causes unintended mutation - when you change what is inside an object by mistake! This can lead to horrible bugs which are difficult to find.
Let's look at this example. We are storing data for two employees of our web agency: Hamza and Alfredo. We are going to represent both of them as object, and list their skills.
const hamza = {
age: 31,
skillLevels: {
javascript: 8,
react: 6
}
};
const alfredo = {
age: 30,
skillLevels: {
photoshop: 9,
illustrator: 7,
afterEffects: 4
}
};
Hamza has changed his mind. After struggling with javascript for so long, he has decided to become a designer.
In his place, a new developer has been employed called Sasha.
Before Hamza begins his new career as a designer, he has one last development job: update script to reflect the new staff changes.
First, he needs to show that the developer is now Sasha. Then he needs to show that he has designer skills, not developer skills.
Finally, it turns out Alfredo lied about this knowledge of After Effects. Cheeky. Best update that skill from his data object.
This is Hamza's attempt at correcting the data:
const sasha = hamza; // So we give Sasha the same developer skills as Hamza;
hamza.skillLevels = alfredo.skillLevels; // Now Hamza's skills are in design
alfredo.skillLevels.afterEffects = 0; // The truth about Alfredo's skill in afterEffects
He thinks he's done a superb job. But has he...
What skills do you think Sasha has? Photoshop and Illustrator, or Javascript and React?
Unfortunately, by changing the property skillLevels in the hamza object, he has inadvertently changed the skillLevels for the Sasha object. Sasha now has the skills of a designer too. Why?? Because both the genie for the hamza object and the genie for the sasha object are sitting on the SAME reference island, looking through a telescope at the SAME ship object.
In other words, they share the same reference.
Poor Hamza's problems haven't finished. This is why he gave up on javascript. If only he knew about The Great Sync.
On the second line of code, he instructs the captain of the hamza ship ( which is also the sasha ship), to instruct the genie crew member called 'skillLevels' to shine his light down on an object reference island - the same island that the 'skillsLevels' genie on the alfredo object ship points at. We can visualise this:
As soon as we try and set Alfredo's AfterEffects skills to 0, we can see how we are changing the value for Hamza's object too! Look at the image below, notice how there is only one object which contains the skills properties. One object, with a shared reference.
This is called unintended mutation.
One way developers try avoid unintended mutation is by cloning objects or arrays first, before we make any changes.
const obj1 = { title: 'obj1 '};
const clone = { ...obj1 };
clone.title = 'obj2'
console.log(obj1.title) // obj1
Cloning an object has ensured the clone genie and the obj1 genie are not sharing the same reference island. They are on separate reference islands, looking up at separate ships.
Just like many things in javascript, it’s easy to skip over what really happens under the hood when we clone an object, and the disastrous consequences of this misunderstanding. Just ask Hamza 😂.
Can't wait to dive deep into cloning in the next post.