Hoisting
Background
Hoisting is one of those terms that every JS dev has heard of because you googled your annoying error and ended up on StackOverflow, where a random person told you that this error was because of hoisting 🙃 So, what is hoisting? (FYI - scope is covered in another post, I like to keep posts small and focused)
If you're new to JavaScript, you may have experienced weird behavior where some variables are randomly undefined, ReferenceErrors get thrown, and so on. Hoisting is often explained as putting variables and functions to the top of the file but nah, that's not what's happening, although the behavior might seem like it 😃
What is hoisting
When the JS engine gets our script (code), the first thing it does is setting up memory for the data in our code. No code is executed at this point, it's simply just preparing everything for execution. The way that function declarations and variables are stored is different.
"Hoisting" refers to the behavior of variables and functions being moved to the top of their scope before code execution. This means that you can use a variable or function before you declare it in your code. This means that regardless of where a variable (or function) is declared within a scope, it is as if the variable declaration is moved to the top of the scope.
It is important to note that only the variable declaration is hoisted, not the variable assignment. In other words, the variable is not assigned a value until the line of code where the assignment occurs is executed.
Hoisting in Variables
There are three ways to declare variables in JavaScript:
var
var is the oldest way to declare variables in JavaScript. Variables declared with var are hoisted to the top of their scope and can be re-assigned a new value.
console.log(x); // Output: undefined
var x = 5;
In this example, the var declaration is hoisted to the top of the code, so the console.log statement does not throw an error. However, the value of x is undefined because the assignment of the value 5 to x is not hoisted.
let
let is a newer way to declare variables in JavaScript. Like var, a let declaration is hoisted to the top of its block scope. However, unlike var, a let variable is not initialized with the value of undefined until the line of code where it is declared is executed. This means that if you try to access a let variable before it is declared, you will get a ReferenceError.
console.log(x); // Uncaught ReferenceError: Cannot access 'x' before initialization
let x = 5;
In this example, the let declaration is hoisted to the top of the code, but x sits in the Temporal Dead Zone (TDZ) until the declaration line is reached. Accessing it before that point throws a ReferenceError.
It is important to note that hoisting behavior with let only occurs within the block scope where the variable is declared. This means that if you declare a let variable within a block (such as a for loop or an if statement), it will only be hoisted to the top of that block, not to the top of the function or global scope.
Overall, while let variables have hoisting behavior like var, they are initialized differently and have block-scoped behavior that make them more predictable and less prone to errors.
const
const is also a newer way to declare variables in JavaScript. const is used to declare variables that have a constant value, meaning that their value can't be re-assigned or re-declared. Variables declared with const have the same hoisting behavior as variables declared with let.
Like let, a const declaration is hoisted to the top of its block scope, but unlike let, a const variable must be initialized with a value when it is declared. This means that if you try to declare a const variable without initializing it, you will get a SyntaxError.
Here is an example to illustrate how const works in JavaScript:
console.log(x); // Uncaught ReferenceError: Cannot access 'x' before initialization
const x = 5;
Like let, const is hoisted but stays in the Temporal Dead Zone until the declaration line — accessing it before that throws a ReferenceError.
It is important to note that hoisting only affects the declarations of variables and functions, not their assignments. This means that, in all cases, the assignment of a value to a variable is not hoisted and must come after the declaration of the variable.
Hoisting in functions
A function is a block of code that performs a specific task and can be reused multiple times in a program. There are two ways to define a function in JavaScript:
Let's take a look at how hoisting works with these two types of functions.
Function declarations
A function declaration is a function that is declared with the function keyword, like this:
function foo() {
console.log("Hello, world!");
}
Function declarations are hoisted to the top of their scope, which means that you can use a function before you declare it in your code (as long as you declare it in the same scope)
foo(); // Output: Hello, world!
function foo() {
console.log("Hello, world!");
}
Function expressions
A function expression is a function that is assigned to a variable, like this:
const foo = function () {
console.log("Hello, world!");
};
Function expressions are not hoisted, which means that you must declare the function before you can use it. If you try to use a function expression before you declare it, you will get a ReferenceError.
foo(); // Uncaught ReferenceError: foo is not defined
const foo = function () {
console.log("Hello, world!");
};
Arrow functions are a special type of function expression. Arrow functions are not hoisted, which means that you must declare the function before you can use it. If you try to use an arrow function before you declare it, you will get a ReferenceError.
foo(); // Uncaught ReferenceError: foo is not defined
const foo = () => {
console.log("Hello, world!");
};
All done! 🎉 Quick recap:
- Functions and variables are stored in memory for an execution context before we execute our code. This is called hoisting.
- Functions are stored with a reference to the entire functions, variables with the var keyword with the value of undefined, and variables with the
letandconstkeyword are stored uninitialized.
I hope that the term hoisting is a bit less vague now that we've looked at what's happening when we execute our code. As always, don't worry if it still does not make a lot of sense yet. You'll get a lot more comfortable with it the more you work with it.
It is generally considered good practice to declare all variables and functions at the top of their scope to avoid confusion and to make it clear what variables and functions are in scope.