Have you ever wondered why you were able to call a function before you wrote it? If this is the case, this article is going to answer this question! We are going to dig into one of the basic concepts in JavaScript – we are going to teach you what is hoisting in JavaScript and why it’s important. Let’s get started!
What is hoisting in JavaScript?
When you’re coding in JavaScript, its engine creates a global execution context, and this context has two phases. The first one is creation and execution. Hoisting happens during the creation phase – it is when the JavaScript engine moves declarations related to variables and functions to the top of your code. This is the feature that is known as JavaScript hoisting.
MDN is a bit more technical in its definition and says that JavaScript hoisting refers to the process whereby the interpreter allocates memory for variable and function declarations prior to execution of the code, but it is basically what we are saying too.
In short, hoisting is JavaScript’s default behavior when its engine moves declarations to the top of the current scope, a function, for example.
Two simple examples of how hoisting works with variables
Both examples below give the same result, let’s go through the code and comments and you’ll see what we’re talking about:
The first example, where we declare a variable at the end of a code:
a = 10; // we assigned 10 to a
element = document.getElementById("example"); // tell JavaScript to find an element
element.innerHTML = a; // tell JavaScript to display a in the element
var a; // this is where we declare variable a
In the second example we declare a variable at the beginning of a code:
var a; // we declared the variable a at the very beginning
a = 10; // and we assigned a value 10 to the variable a
element = document.getElementById("example"); // we tell JavaScript to find an element
element.innerHTML = a; // we tell JavaScript to display a in the element
So, as we already said and explained through the two examples above, variable hoisting means the JavaScript engine moves the variable declarations to the top of the script. This is why we didn’t get an error – in the first example, the JavaScript engine moves the variable declaration that we positioned at the very bottom to the top of our code.
What’s up with the let and const keywords and hoisting?
If we define variables with let and const, these variables will be hoisted to the top of the block, which means the block of code knows the variables are there, but the JavaScript engine will not initialize them, because they cannot be used until they have been defined. What will happen? If we use let variable before we declare it, JavaScript will throw us a ReferenceError, and if we use a const variable before we define it, JavaScript will recognize it as a syntax error and the code will not run. As simple as that.
So, what can we learn from that? Remember one simple rule. Declare your variables at the top, at the beginning of a scope.
Only variable declaration and not the initialization is hoisted to the top
What does that mean? As always, practice overpowers theory, so let’s look at examples:
In the first example we declare and JavaScript engine initializes both var variables and displays them:
var a = 1; // we declare and Javascript engine initializes a
var b = 2; // we declare and Javascript engine initializes b
myElement = document.getElementById("example"); // JavaScript finds our element
myElement.innerHTML = a + " " + b; // JavaScript engine displays a and b as 1 2
In the second example we initialize variable a at the very beginning of a scope, but the b variable at the end. Is the result the same? Let’s see what happens:
var a = 1; // JavaScript engine initializes a
myElement = document.getElementById("example"); // JavaScript finds our element
myElement.innerHTML = a + " " + b; // We tell JavaScript engine to display a and b
var b = 2; // JavaScript engine initializes b
Nope, unfortunately, the result is not the same. Why? This is because JavaScript engine hoists at the top only the declaration (var b) and not initialization (=2), this is why the value of b is undefined. So, if we use a variable in the code and then we declare and initialize it, when the value is used it will be its default initialization – in our case this is undefined because we declared our variable with var, otherwise JavaScript engine will return uninitialized.
Let’s take a look at another example:
function myCode(){
a = 1;
let b = 2;
}
myCode();
console.log(a); // 1
console.log(b); // JavaScript engine throws ReferenceError : b is not defined
So, what is happening here? We created a function called myCode() and we placed a variable a, but we didn’t declare it by using either var, let or const. In addition to that, we created a variable b and declared it with let.
What happened with our undeclared variable a? JavaScript engine assigned that variable to the global scope and this is why we can print it outside our myCode() function. But, what happened to variable b? The scope of that variable is limited to the function, this is why it is not available outside the function and JavaScript throws us a ReferenceError.
What about function hoisting?
Function hoisting is similar to variable hoisting, however different. JavaScript engine also hoists the function declarations, which means it moves the function declarations to the top of the scope before code execution. This means that it doesn’t really matter where we declare functions (and variables), JavaScript will move them to the top of their scope. In addition to that, it doesn’t really matter whether their scope is global or local. And this is the answer to the question from the introduction – this is exactly the reason that allows us to call functions before we write them in our code.
Function hoisting is helpful because it makes your code more readable, in addition to that it can apply an abstract level logic at the very start of the source code.
But, you said function hoisting is different from variable hoisting. How is that? Compared to variables, a function declaration doesn’t just hoist the function’s name, but also the actual function definition. Let’s take a look at an example for an additional explanation:
hoistingFunction(); // "An example that shows functions hoist too!"
function hoistingFunction() {
console.log("An example that shows functions hoist too!");
}
// Outputs: "Yes!"
functionHoisted();
function functionHoisted() {
console.log("Yes!");
}
Let’s have a look at a few more examples with functions:
let a = 10;
b = 5;
let calculate = add(a, b);
console.log(calculate);
function add(c, d){
return c + d;
}
See what we are talking about? We called the add() function before we defined it – this is what function hoisting is about, and the code above is the same as the one below:
function add(c, d){
return c + d;
}
let a = 10;
b = 5;
let calculate = add(a, b);
console.log(calculate);
So, what is going on here? Hoisting is going on – during the creation phase of the execution context, the JavaScript engine repositions the add() function declaration. Or, if you want more precision, an object of the function type and a function reference called add that refers to the function object is created.
What about function hoisting for function expressions?
Yes, the code above is all nice and passes a test for a function declaration to be read like this, so where is the difference between hoisting variables and hoisting functions? The difference arises when we try to replicate the same thing for a function expression. If you try to do that, the JavaScript interpreter will throw you an error.
// TypeError: Undefined
nonHoistedFunction();
var nonHoistedFunction = function () {
console.log("This doesn't work!");
};
So, when the function is a part of an overall expression, it will not be hoisted. In addition to that, remember we said at the beginning – declarations are hoisted, but initializations are not? Well, that is something that proves to be true here too.
Maybe you’re wondering what will happen if we use a named function expression. Let’s take a look.
// ReferenceError: functionWithName is not defined
functionWithName();
// TypeError: undefined is not a function
variableName();
var variableName = function functionWithName() {
console.log("Definition not hoisted!");
};
Basically, like variables, the JavaScript engine hoists the function declarations – it moves them to the top of the script. However, the example above proves the function’s name does not get hoisted if it is part of a function expression. Let’s have a look at another similar example. We will take an example from above, where we will change the add from a regular function to a function expression:
let a = 10;
b = 5;
let calculate = add(a, b);
console.log(calculate);
var add = function(a, b){
return a + b;
}
So, what is going on here? JavaScript engine throws an error: “TypeError: add is not a function”. Why? During the creation phase of the execution, the JavaScript creates the add variable and initializes its value to undefined. So, when JavaScript tries to execute the code, the add is undefined and therefore not a function.
Ok, so this is the case with regular or traditional functions, but what about arrow functions? Let’s change the regular function from above to the arrow function:
let a = 10;
b = 5;
let calculate = add(a, b);
console.log(calculate);
var add = (a, b) => a + b;
JavaScript engine will throw the same error (“TypeError: add is not a function”) as in the example above with the function expression, because the same as function expressions, the arrow functions aren’t hoisted.
So, what did we learn – let’s summarize the basics!
In terms of hoisting, we learned that it occurs during the creation phase of the execution context. During that process, variable and function declarations are prioritized and moved to the top of the script.
Variables declared with the var keyword are hoisted and initialized, but that is not the case with variables that are declared with the let keyword – these variables are not initialized.
In terms of hoisting, functions behave similarly to variables, except that function expressions and arrow functions aren’t hoisted. We’ve shown that through a few examples. In general, you should keep in mind a good programming practice while writing functions in JavaScript. A good way to avoid problems is to place the variables at the top of the local area in the function, which actually means you will hoist them yourself.