Over the past few days we have journeyed through a visual exploration of some of javascript's basic concepts, encountering genies, flying ships and aggressive captains who like to shout at their crew members.
When we began this short email course, we looked at a code snippet showing a deep copy of an object. It is now time to look at the code again, and identify the patterns we have already imagined.
I use the term patterns loosely. Obviously these are not programming patterns for applications. That will come. First, the patterns we want to practise identifying are the specific ways we apply fundamental concepts. The aim is to prove it is possible to add a visual dimension to the process of reading or writing code.
If a single genie🧞 or flying ship🚢 comes to mind when practising javascript, I have succeeded. It does not even matter if you can't recall what the analogies represent. The point is you are interacting with the code on an entirely different level, and that leads to greater understanding.
With all that said, let's dive in. Here is the code snippet again incase you have forgotten:
// DATA
const app = {
blogsPublished: 1,
blogsUnPublished: 10,
users: [
{
userId: 32323,
username: 'CodeMaster',
posts: [
{
title: 'learning Javascript'
}
]
}
]
};
// CAPITALISE THE TITLE IN THE FIRST POST OF THE FIRST USER IN OUR DATA
// DO NOT MUTATE THE ORIGINAL APP OBJECT
const incorrectTitle = app.users[0].posts[0].title;
const correctTitle = incorrectTitle[0].toUpperCase() + incorrectTitle.slice(1);
const updatedApp = { ...app };
updatedApp.users = [ ...app.users ];
updatedApp.users[0].posts = [...app.users[0].posts];
const updatedFirstPost = { ...updatedApp.users[0].posts[0] };
updatedFirstPost.title = correctTitle;
updatedApp.users[0].posts[0] = updatedFirstPost;
Does the code look any different to you than at the start of the course? Probably not 😂. But the magic happens when start looking a bit closer.
It all begins with the app variable. app is simply a genie, sitting on a rocky reference island minding his own business, looking up at the respective flying object ships! That ship is the one we want to clone, and we can see the updatedApp variable is the first step to achieving this.
But before we get to this step we come across our first and most important pattern.
I will never forget the first time I had to access a function inside an array, which was inside another array, which was inside an object. This 'ship traversal' is pretty common in javascript, and can look super confusing. But we now know what is going on here, every time we see a dot or [ ]. These are instructions - in my humble opinion, a little too aggressive instructions - made by the captain of the object or array vessel. He is trying to find an island, and he needs a crew member to point👉 his light.
The trick is to remember what island the captain finds for us, for each dot or square bracket access. That's how you can carefully work your way through the expression until you arrive at the answer. Let's take a look:
const incorrectTitle = app.users[0].posts[0].title;
** For the sake of brevity, this sequence will describe properties pointing at objects or arrays. Just remember, they never directly point at them, but rather their reference values.
The final string value is where our named genie, the variable incorrectTitle, will place his ***first 3 letters of assign*** on the island loo.
If we were to draw or visualise this sequence, it would look something like this:
After accessing the string value 'learning Javascript', we now attempt to edit the string so we capitalise the first letter. The name of the next variable, correctTitle, tells us this is exactly what the expression will try to do. We don't even need to analyse the code to know the first and most important rule about primitives like strings: we can't mutate them, we CAN'T MESS WITH NATURE.
So no matter what method we choose, either way, we need to make sure we create a NEW string, and not try edit the existing.
const correctTitle = incorrectTitle[0].toUpperCase() + incorrectTitle.slice(1);
The first part of the expression - incorrectTitle[0].toUpperCase() - simply finds the capitalised "L" string and returns it.
Now that we have the L, we need the rest of the string so we can make up the full 'Learning Javascript' string. That's the second part of the expression: incorrectTitle.slice(1) This slice method will slice the string after the second letter ( ie [1] ), and return the rest of the string: "earning Javascript". All we need to do is append them together with +.
But wait! Isn't [0] and [1] array square bracket notation? And using the + sign implies we are creating a string value. Sounds like a flaw in our mental model, because we don't create strings, and strings aren't ships with angry captains!
In reality, our mental model is correct. It is a string primitive, and technically we shouldn't be able to do things we would do on an object. But here's one quirk worth remembering when it comes to strings: the javascript engine will allow us to access strings like arrays using a special String constructor function. Under the hood it would look something like this:
new String("learning").charAt(0);
// OR
new String("learning")[0]
More on that later, for now worth only remembering that we can access string characters with array indices. At the end of the day strings are always primitives and can't directly be changed.
Objects, on the other hand, are very susceptible to mutation.
And that leads us to the third and final pattern we can decipher in the code snippet using our mental model.
The objective was to create a duplicate of the data stored in the app variable.
// DATA
const app = { ..... }
The problem is our app data is one giant collection of objects and arrays. There are tons of ships flying around there. As we know well from our understanding of reference values, if we want a true duplicate of an object we need to make sure that any objects that a property points to is also cloned. This is called DEEP CLONING or deep copy.
If you recall from the previous post on object cloning, when a clone is made the new and existing genie properties meet to discuss the location of any values they store. Since genies cannot see ships directly, they can only provide the coordinates of the reference islands.
Once we have a reference island, we then to repeat the process by cloning the object that reference is pointing at. EG
const updatedApp = { ...app };
updatedApp.users = [ ...app.users ];
To achieve a deep copy, we continue this cloning process until every genie crew member in an object is pointing at a reference of completely new object and array.
Exhausting, isn't it? Generally you try to avoid creating complex object/array structures if you know they will need to be cloned to make any changes. Sometimes, you might even not want to bother with cloning an object to make changes - as long as you are intentional about it, and you know for sure the object is not being referenced in other parts of your code.
Thing is, that usually becomes problematic in a bigger application. Fortunately, this is often taken care of for us by libraries and frameworks. In Redux, a state management library for React, deep cloning is handled behind the scenes so you might not even be aware it is happening!
For the purposes of understanding how javascript works, it is important we understand the problem tools like Imer, Immutable, Underscore.js and so many others solve for us.
The final noticeable pattern in this code is the way we can store expressions in variables. When we start learning javascript, the examples of variables we use always tend to use literals as the value:
const data = {};
const str = 'Hello world';
const result = false;
But variables can also store an end result. An expression is any time we try to access a value. The could be a value inside an object or array, a check to see if something is true or false, or perhaps the return value of a function. In our model, we visualise this as simply giving a genie a name, and assigning him to the resulting island value.
In our code snippet, incorrectTitle, correctTitle and updatedFirstPost are all genies who have been assigned to the result of an expression.
const incorrectTitle = app.users[0].posts[0].title;
const correctTitle = incorrectTitle[0].toUpperCase() + incorrectTitle.slice(1);
const updatedFirstPost = { ...updatedApp.users[0].posts[0] };
They have two main uses:
If you have ever read someone else's code (or your own) and thought to yourself "I have absolutely no idea what is going on here", the problem is not always your javascript skills. It can often be the way the code is written, and in particular, the use of variables or lack thereof.
A well named genie can go a long way to explaining what is happening in the code. For example, we could improve the last line above with this:
// BEFORE
// const updatedFirstPost = { ...updatedApp.users[0].posts[0] };
// BETTER
const firstPost = updatedApp.users[0].posts[0];
let updatedFirstPost = { ...firstPost };
The thing with expressions is that they always result in a value, even if it's not the value we were hoping for. When you ask a genie to find you an island, he WILL find you an island. If he has no coordinates for the value you want, he will look for the next most obvious value: undefined.
Sometimes we are unsure our expressions will find the correct value. The most common example is when a function executes and we are not sure what the result will be. Saving the result into a variable helps us move the value around without having to execute the function again. For example:
const attemptToFindUser = checkUserExists(); // returns an object with success or error properties
if (attemptToFindUser.success) redirectToPage('/authorised');
else logError(attemptToFindUser.error)
If you're still reading this post, we can assume you found something about this form of learning javascript that interests you. Is it what you initially expected? Were you surprised at any point? Let me know in the comment box below.
We're not finished yet! I still have one more resource to send you, so look out for my next email ✌️