Variable and Function Hoisting in JavaScript

Posted by e-be-walk on September 29, 2018

It’s important to understand how hoisting can affect scope. There’s two parts to hoisting that affect how are declarations are scoped- variable hoisting and function hoisting. Starting with variable declaration, it is important to note that with ES6 and the introduction of let and const, variable hoisting also had new, unique behavior. When using var to declare your variables, the JavaScript engine treats all declarations as though they are within the global scope regardless of where they were actually declared. This is essentially hoisting the variable to the top level and it doesn’t matter whether the var variable is within or outside the function. This means that the variable is actually available before it is declared. Let’s take a look at an example:

Notice that the first console.log(salary); doesn’t throw and error- it returns undefined. This means that the JavaScript engine found the var salary declared below but during this first console.log did not yet have the value of that variable. It’s been hoisted to the top. This powerful behavior can lead to bugs in our code if we’re not careful about how we declare variables.

If we use let or const instead of var within this example, the console will return an error and terminate all further execution. If you are using babel the behavior for let or const in this example, the behavior for this example is essentially the same- the JavaScript Engine sees the declaration but does not know the value returning undefined for the first console.log. It is important to remember that const declarations cannot be reassigned and must be declared at the time they are initialized. Otherwise, in this particular example we’re not operating on the data and all three behavior relatively the same. It gets a bit more complicated when we throw functions into the mix.

Block level scoping

Block-level scoping helps to provide more control over a variable’s lifecycle. Block-level declarations were introduced with ES6 and are made within a { } block. We’re going to demonstrate block-level declarations using let and const to see how that affects the availability of the variable.

let Declarations

The syntax of let is very similar to var. You want to place your let declarations a the top within a block so they’ll be available within that entire block. Let’s take a look at an example that uses if to evaluate for a condition.

This throws an error because at the first console.log on line 2, the Javascript engine does not see salary defined and nor does it see the let declarations within the if/else blocks. The error won’t allow us to call the function on line 13- getRaise(yearsEmployed = 6);. If we simply comment out that console.log, our function will fire and output the predicted result to the console.

We can’t use const for this example because the value of the variable salary changes with the condition of yearsEmployed. Using const for any salary in this example will cause an error.

Function Hoisting

Variable hoisting is not the same as function hoisting- it is unique in ways of its own. Remember there are 2 ways of creating functions- Function Declaration and Function Expression.

In Javascript, function declarations hoist the function definitions. This means that these functions can be used even before they are declared. The syntax for function declaration is simple and the console.log is interpreted even though the function is called prior to the function definition:

A function expression refers to a function defined within an expression. A function expression can be anonymous so we are free to use arrow function syntax in function expressions. They are not, however, hoisted and cannot be used before they are defined.

Breakdown

Let’s break this down looking at another example. We’re going to essentially use the same example switching sequence of execution, variable declarations, or function declarations to see how these impact hoisting. In our first example we will use var for our variable declarations and for a function expression as well.

Interesting, right? In this example we have a var salary in the global scope but within the function the first time it is called it is undefined. What gives? Well, because of the way the JavaScript engine runs the code it is looking for salary within the function block. Since there is a var salary within the block it recognizes it is there with the first console.log but does not yet know it’s value. The var salary ='$1000'; is in the global and the console.log isn’t looking for it. Look at how it changes the outcome by simply removed the var declaration within the block:

Crazy, right? Now it knows that we are adjusting the value of the var in the global scope when we execute the block. It shows the value of the first salary and changes the value. It would have the same result of moving the first var salary to within the block.

Now this is also a function expression- we are assigning the var hoisting a function definition. Remember- we can’t execute a function expression before it is defined:

If we change it to a function declaration though…

It works!

So now let’s return the example to it’s original format, but change out the vars with the new, fantastic variable declarations let or const. If we change that first var salary to const salary we shouldn’t be able to overwrite the value in our block, right?

Wrong! It still has the same behavior it did in our first example because const salary is in the global scope. The block recognizes there is a salary variable within it and ignores the global salary. If we were to use let in this same context, it would give the same result. If we put it within our block we get the expected behavior- it throws an error when we try to overwrite the value:

Now, if we try to use let within the block what do you think will happen? You might expect it to have the same behavior- returning undefined in the first call and logging the ’$5000’ as part of the second console.log, right?

Surprisingly- no! It doesn’t recognize the variable declaration within the block if you use let and it will throw and error halting further execution. It’s the same if you switch that let in the 7th line to const. If you are using let or const within the block the only way this will work is if it is declared in the top line within the block, and omitting the keyword when you try to overwrite the value:

Pretty neat, huh? Little differences in the way we make declarations— in both functions AND variables can make a big difference in the way JavaScript reads our code. This is so important to keep in mind because it also affects the scope in which these declarations are hoisted.