Hoisting: Why VAR is a no-go
So, you can write JavaScript code that executes, but does it execute as expected?
Do you ever wonder what’s going on behind the IDE? Do you know how your code really works?
Let’s consider this code snippet; hope you like dogs!
(Don’t worry, I won’t be using var
for long.)
var dogs = [
{ name: 'Golden Retriever', age: 6, loved: true },
{ name: 'Bernese Mountain Dog', age: 2, loved: true },
{ name: 'Lab', age: 5, loved: true },
{ name: 'Dalmatian', age: 3, loved: true }
];function sortByKey(dogs, key) {
return dogs.sort((dogA, dogB) => {
return dogA[key] - dogB[key];
});
}var alphabetizedDogs = sortByKey(dogs, key);var key = 'age';console.log(alphabetizedDogs);
Imagine you are JavaScript (imagine having so much power, yet so little structure)…
How would you interpret this code?
The Creation Phase — JS takes a look at the code to determine which variables and functions need to be stored in memory before truly digging into the execution of the code. During this phase, something called hoisting takes place. Essentially, JS rearranges the code in a particular way. JS is a single-threaded language, meaning it interprets code one line at a time. The rearrangement usually follows this pattern, with declarations at the top (function, let, const, var, class, etc.):
//From top to bottom: var dogs, alphabetizedDogs, key = undefined; //declarations
function sortByKey(dogs, key) { //function definitions
return dogs.sort((dogA, dogB) => {
return dogA[key] - dogB[key];
});
}dogs = [ //variable assignment
{ name: 'Golden Retriever', age: 6, loved: true },
{ name: 'Bernese Mountain Dog', age: 2, loved: true },
{ name: 'Lab', age: 5, loved: true },
{ name: 'Dalmatian', age: 3, loved: true }
];alphabetizedDogs = sortByKey(dogs, key);key = 'age';
console.log(alphabetizedDogs);
At the very top, JS identifies the variable declarations. You might be wondering about the values of these variables. What are they?
Well, initially, they are all undefined
but as JS works through each line of code in this order, each variable is assigned the intended value…except key
doesn’t get its value in time.
A glimpse into the JS mind:
Oh awesome, I have three variables right now, but they haven’t been assigned values yet. I’ll go ahead and make them all
undefined
until I recognize the assignment=
operator. Great, and I have a function, too. Okay…so nowdogs
is an array,alphabetizedDogs
is assigned the function I saved earlier, and…oh yeah, it looks like I have to invoke thesortByKey
function and assign its return value toalphabetizedDogs
. Okay, got it! Let me go find that function and execute, then I’ll pick up where I left off.Alright, now that I’ve executed that function and assigned a value to
alphabetizedDogs
, I can continue by assigningkey
a value of'age'
. Great, let’s logalphabetizedDogs
to the console! It’s another job well done today.
Here is my output. Is this what you expected to see?
[
{ name: 'Golden Retriever', age: 6, loved: true },
{ name: 'Bernese Mountain Dog', age: 2, loved: true },
{ name: 'Lab', age: 5, loved: true },
{ name: 'Dalmatian', age: 3, loved: true }
]
Given the fact that I’m sorting by age (youngest to oldest), I expected this output:
[
{ name: 'Bernese Mountain Dog', age: 2, loved: true },
{ name: 'Dalmatian', age: 3, loved: true },
{ name: 'Lab', age: 5, loved: true },
{ name: 'Golden Retriever', age: 6, loved: true }
]
Okay…what went wrong?
Well, at the time I executed the function
sortByType()
, the argument passed for the parameter ofkey
wasundefined
. It only had the value I gave it because I hadn’t reached its assignment yet. Sinceundefined
isn’t a key on any of the array elements, there wasn’t any criteria by which to sort. I did the best I could (not sorry).
How can I make JS log the expected output?
I have a few options.
- Move the
key
assignment toward the top of my code, prior to the invocation ofsortByType
. - Ditch
var
— start usinglet
andconst
from ES6!
Let’s try both.
Consider how this code changes when var
is replaced by let
.
let dogs = [
{ name: 'Golden Retriever', age: 6, loved: true },
{ name: 'Bernese Mountain Dog', age: 2, loved: true },
{ name: 'Lab', age: 5, loved: true },
{ name: 'Dalmatian', age: 3, loved: true }
];function sortByKey(dogs, key) {
return dogs.sort((dogA, dogB) => {
return dogA[key] - dogB[key];
});
}let alphabetizedDogs = sortByKey(dogs, key);let key = 'age';console.log(alphabetizedDogs);
Now, when I run this code snippet, I see this error:
Cannot access key before initialization.
Not only will JS refuse to finish its execution, but it also provides you with this helpful error message.
Wait, hold on! I want to invoke
sortByKey
with two arguments, one is thedogs
array I already know about, and the other iskey
…what’skey
???
Hoisting still occurs for these variables, but instead of receiving an undefined
value from the start, the const
and let
declarations are not initialized, which ultimately catches the bug before it’s able to cause any problems!
let dogs = [
{ name: 'Golden Retriever', age: 6, loved: true },
{ name: 'Bernese Mountain Dog', age: 2, loved: true },
{ name: 'Lab', age: 5, loved: true },
{ name: 'Dalmatian', age: 3, loved: true }
];function sortByKey(dogs, key) {
return dogs.sort((dogA, dogB) => {
return dogA[key] - dogB[key];
});
}let key = 'age';let alphabetizedDogs = sortByKey(dogs, key);console.log(alphabetizedDogs);
Perfect! As long as key
is assigned its value prior to being referenced, we’re all set. Var
comes with tons of bug-prone deficits; one of which is that undefined variables can make their way through code execution.
In my next article, I’ll be diving into let
and const
more deeply. I will also introduce the concept of scope: global, local, and block. Stay tuned!