JavaScript and Hoisting
Published: 2021-01-13 16:56:39

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? 
![Hoisting a horse](https://dev-to-uploads.s3.amazonaws.com/i/16b7cvbdg4l8mf9nxahz.jpg)

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: ** <uninitialized>
* 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!