At first glance, anyone first learning about what hoisting is could easily write it off as one of JavaScript's many weird quirks. Especially programmers coming from stricter languages like C++, that require you to get your code right the first time. The existence of hoisting is courtesy of JavaScript’s relentless efforts to make your code work which makes things like “spaghetti code” and “side effects” easy to write. However, there's a lot more to hoisting than what meets the eye here.
What is Hoisting?
Essentially hoisting is the process through which the declaration of variables and functions in your code are metaphorically lifted up from your code and stored in what is called a “lexical environment”. The lexical environment in JavaScript is essentially a construct within the JavaScript engine that holds identifier-variable mapping. In layman’s terms, it’s best explained as a sort of limbo above your code that contains the names of all your declared variables and functions so that they can be referenced later on at any point without throwing reference errors.
Hoisting in Action (Variables)
Take a look at the code below. What do you expect it to log?
var greeting = "Hello";
console.log(`${greeting}!!!`) ;
If you answered
Hello!!!
Then you’re absolutely correct. Now what if I told you that the code blow this sentence still logs something instead of throwing a reference error.
console.log(`${greeting}!!!`); // logs “undefined!!!”
var greeting = "Hello";
Chronologically you’d expect this code to log an error considering the string variable greeting is assigned the value of "Hello" after the console.log(). However because the declaration of greeting was hoisted into the lexical environment before the code’s runtime, the variable is accessible to the console.log() as it’s referencing the name of the variable from the lexical environment rather than from the line it’s actually written on below, thus it’s treated as if it were written above the console.log()
Here’s what this code actually looks during runtime:
var greeting = undefined;
//The above declaration is stored in the lexical environment
console.log(`${greeting}!!!`);
greeting = "Hello"; //greeting is assigned a value before the function is invoked
Now here’s where things get tricky. If you were to declare the variable with let instead of var the code would log an error, specifically because the variable is declared using let. This is because var and let have different properties when it comes to hoisting. When a variable declared with let or const is hoisted, it bears no value and remains uninitialized, however when a variable declared with var is hoisted, it is initialized with a value of undefined. Take a look at that code before runtime:
console.log(`${greeting}!!!`); // logs “undefined!!!”
console.log(`${testGreeting}!!!`); // Throws “variable uninitialized” error
let testGreeting = "Howdy";
var greeting = "Hello";
Now look that same code at runtime:
let testGreeting;
var greeting = undefined;
console.log(`${greeting}!!!`); // logs “undefined!!!”
console.log(`${testGreeting}!!!`); // Throws reference error
testGreeting = "Howdy";
greeting = "Hello";
The Absence of Hoisting
If you don’t understand why hoisting is important, take a look at the exact same code in C++:
int main(){
// prints hello world
string hello = "Hello World";
cout<< hello;
return 0;
}
Now if you did the same thing with this code, no hoisting occurs, therefore if the function isn’t logged into the console, you are instead thrown a reference error because unlike JavaScript, C++ is a strictly typed language with no hoisting. See below:
int main(){
cout<< hello;
string hello = "Hello World";
return 0;
}
Hoisting in Action (Functions)
Hoisting for functions and variables work largely the same. Take a look at the example below.
var greeting = "Hello";
myFunction(); // logs “Hello” to the console
function myFunction(){
console.log(greeting);
}
The declaration of all functions are hoisted into the lexical environment, and thus are treated as if they were written at the top of their respective scope. So as you can see, the code above logs greeting to the console even though the function was invoked before the function was even written. This is the result of javascript scanning your code at run-time and hoisting all instances of function and variable declaration into the lexical environment. There is however, one caveat:
myFunction(); // logs undefined to the console
var greeting = "Hello";
function myFunction(){
console.log(greeting);
}
When it comes to functions, their scope is limited to everything above the point at which it is invoked. Therefore in the above case only the variable accessible to myFunction() is var greeting = undefined; which is located in the lexical environment at run-time.
Why Understanding Hoisting Is Important
As you’ve learned hoisting is the result of JavaScript essentially taking note of all of your functions and storing them in a space called the lexical environment where it can be referenced and accessed from anywhere in your code so long as it belongs in the correct scope. The reason why this is important is because the side-effects that can be caused by hoisting’s existence strongly influences the way we structure our programs, and with a deep enough understanding one could even leverage the existence of hoisting to spot and eradicate bugs or side-effects that otherwise wouldn’t be evident without knowledge of the concept.
Top comments (0)