Hoisting can be a difficult concept for people just starting out with JavaScript. Before we get into the how's and why's of hoisting, let's go over what hoisting actually is. In the simplest terms:
Hoisting is JavaScript's default behavior of moving all declarations to the top of their scope before the code is executed.
Hoisting was originally written into JavaScript to allow functions to be used before declaration and the hoisting of variables was simply a byproduct of that feature.
Why would we even need to use functions before they are declared?
- It's necessary for things like mutual recursion, which is a subject for another day.
- Some people think code looks cleaner when written this way. (I agree!)
A lot of people think that hoisting actually moves your code around, but JavaScript just reads your code before executing. This step is called compilation. JavaScript takes into account every declaration in your code and is then aware of those declarations prior to execution.
var
var
is function scoped When we define variables using var
, they are lifted to the top of their scope during compilation, no matter where the variable has been declared. When we talk about the scope of var
it's important to mention that the scope is either global or the top of the function in which they were defined. Even if that variable is declared inside of a conditional or a loop. However this applies only to the declaration, not the assignment. Here's an example:
console.log(yearBorn)
var yearBorn = 1990
What do you think this code would log to the console?
If you guessed undefined
, you'd be correct! This is because JavaScript only hoists the declaration, not the assignment. This isn't a helpful error. It can make bugs difficult to find and remedy.
Thankfully, JavaScript gave us let
and const
with ES6! The errors we get using let
and const
are significantly more helpful than just a plain ole undefined
.
const
const
means that the identifier can't be reassigned. They keep constant values that cannot be updated or re-declared. As an example, let's say you planned on making a really nice dinner when you get home from work. It turns out you had to stay late today and don't really have the time or energy to make your special dinner.
const dinner = "Beef Wellington"
dinner = "Frozen Pizza"
console.log(dinner)
// Uncaught TypeError: Assignment to constant variable.
Doing something like this will cause us problems as well:
const dinner = "Beef Wellington"
const dinner = "Frozen Pizza"
console.log(dinner)
// Uncaught SyntaxError: Identifier 'dinner' has already been declared
This doesn't mean const
is a bad thing! We may have important values in our that we want to make sure can't be re-assigned or re-declared. Specific formulas or numbers as just a couple examples. Something else of note about const
: If the value of our variable declared with const
is an array or object, the properties within them can be updated. It's best practice to use const
unless you know the declared variable will be re-defined at some point. That brings us to let
!
let
Let's go back to our dinner example.
let dinner = "Beef Wellington"
dinner = "Frozen Pizza"
console.log(dinner)
// Frozen Pizza
let, const and Hoisting
Now that we know what let
and const
are and what they do, we can talk about them in the context of scope and hoisting. let
and const
are both block scoped, mean they exist only in the block where they were declared. This differs from var
in that if a variable is declared with let
or const
it is hoisted to the top of its scope which could be global, a function or inside of a code block like a conditional or loop.
let ingredients = ["Spaghetti", "Meatballs", "Garlic Bread"]
function makeDinner(ingredients) {
let firstIngredient = ingredients[0]
if (firstIngredient.length > 5) {
let longIngredient = true
}
return longIngredient;
}
console.log(makeDinner(ingredients))
// Uncaught ReferenceError: longIngredient is not defined
console.log(firstIngredient)
// Uncaught ReferenceError: firstIngredient is not defined
Let's forget about the code we've written up above and say we forget to gather our ingredients before making dinner.
function makeDinner(ingredients) {
var firstIngredient = ingredients[0]
if (firstIngredient.length > 5) {
var longIngredient = true
}
return longIngredient;
}
console.log(makeDinner(ingredients))
let ingredients = ["Spaghetti", "Meatballs", "Garlic Bread"]
// Uncaught ReferenceError: Cannot access 'ingredients'
before initialization
And with var
:
...
console.log(makeDinner(ingredients))
var ingredients = ["Spaghetti", "Meatballs", "Garlic Bread"]
// Uncaught TypeError: Cannot read property '0' of undefined
In short, forget about var
and welcome let
and const
into your code with open arms.
Here is a little hoisting cheat sheet:
var
- Hoisted? yes
- Initial Value: undefined
- Scope: function
let & const
- Hoisted? no (technically yes, but not in practice)
- Initial Value: **
- Scope: block
Function Declarations
- Hoisted? yes
- Initial Value: the actual function
- Scope: block (this is only true when using
'strict'
mode, which you should be!)
Function Expressions and Arrow Functions
- In this case, it depends on whether they were declared using
var
,let
orconst
. They will behave the same way variables do with those declarations.
That's a pretty broad and simple explanation of hoisting. I hope it helps!
Top comments (0)