DEV Community

Cover image for JavaScript and Hoisting
Ronnie
Ronnie

Posted on • Edited on

JavaScript and Hoisting

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

What do you think this code would log to the console?
Hoisting a horse

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And with var:

...
console.log(makeDinner(ingredients))
var ingredients = ["Spaghetti", "Meatballs", "Garlic Bread"]
// Uncaught TypeError: Cannot read property '0' of undefined
Enter fullscreen mode Exit fullscreen mode

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 or const. 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)