DEV Community

loading...
Cover image for JavaScript Hoisting

JavaScript Hoisting

Patricia Nicole
playing with web fiery
・6 min read

[JS#5 WIL πŸ€” Post]

JavaScript hoisting refers to the process where the compiler allocates memory for variable and function declarations prior to execution of code [1]. That means that declarations are moved to the top of their scope before code execution regardless of whether its scope is global or local.

Table Of Contents

πŸ“Œ Javascript Hoisting

Conceptually speaking,

hoisting is the compiler splitting variable declaration and initialization and moving only the declarations to the top of the code.

Thus, variables can appear in code before they are even defined. However, the variable initialization will only happen until that line of code is executed.

The code snippets below show hoisting in action. The first snippet shows how the code is expected to be written: declare the function first and use/invoke it after.

function printIamHoisted(str) {
   console.log(str);
}

printIamHoisted("Am I hoisted??")
Enter fullscreen mode Exit fullscreen mode

On the snippet below, the method is invoked first, and the function declaration is written next. However, both of them have the same output - they print the string Am I hoisted?

printIamHoisted("Am I hoisted?")

function printIamHoisted(str) {
   console.log(str);
}
Enter fullscreen mode Exit fullscreen mode

So even though the function is called before it is written, the code still works. This happens because of how context execution works in Javascript.

πŸ“Œ Javascript Context Execution

When a Javascript engine executes code, it creates execution context. Each context has two phases: creation and execution.

πŸ“Œ Creation Phase

When a script executes, the JS engine creates a Global Execution Context. In this phase, it performs the following tasks:

  • Create a global object window in the web browser
  • Create a this object binding which pertains to the global object
  • Setup memory for storing variables and function references
  • Store the declarations in memory within the global execution context with undefined initial value.

Lookin back at this snippet,

printIamHoisted("I am hoisted!!!")

function printIamHoisted(str) {
   console.log(str);
}
Enter fullscreen mode Exit fullscreen mode

the global execution context at this phase, would somehow look like this Creation Phase.

πŸ“Œ Execution Phase

At this phase, the JS engine executes the code line by line. But by virtue of hoisting, the function is declared regardless of line order, so there is no problem calling/invoking the method prior the declaration.

For every function call, the JS engine creates a new Function Execution Context. This context is similar to global execution context, but instead of creating the global object, it creates the arguments object that contains references to all the parameters passed to the function. The context during this phase would look somewhat like :
Execution Phase

πŸ“Œ Only the declarations (function and variable) are hoisted

JS only hoists declarations, not initializations. If a variable is used but it is only declared and initialized after, the value when it is used will be the default value on initialization.

πŸ“Œ Default Values: undefined and ReferenceError

For variables declared with the var keyword, the default value would be undefined.

console.log(hoistedVar); // Returns 'undefined' from hoisted var declaration (not 6)
var hoistedVar; // Declaration
hoistedVar = 78; // Initialization
Enter fullscreen mode Exit fullscreen mode

Logging the hoistedVar variable before it is initialized would print undefined. If however, the declaration of the variable is removed, i.e.

console.log(hoistedVar); // Throw ReferenceError Exception
hoistedVar = 78; // Initialization
Enter fullscreen mode Exit fullscreen mode

a ReferenceError exception would be thrown because no hoisting happened.

πŸ“Œ let hoisting: Temporal Dead Zone (TDZ)

Variables declared with let and const are also hoisted. However, unlike variables declared with var, they are not initialized to a default value of undefined. Until the line in which they are initialized is executed, any code that access them, will throw an exception. These variables are said to be in a "temporal dead zone" (TDZ) from the start of the block until the initialization has completed. Accessing unintialized let variables would result to a ReferenceError.

{ // TDZ starts at beginning of scope
  console.log(varVariable); // undefined
  console.log(letVariable); // ReferenceError
  var varVariable = 1;
  let letVariable = 2; // End of TDZ (for letVariable)
}
Enter fullscreen mode Exit fullscreen mode

The term "temporal" is used because the zone depends on the execution order (referring to time - temporal) rather than the order in which the code is written (position). However, the code snippet below will work because even though sampleFunc uses the letVariable before it is declared, the function is called outside of the TDZ.

{
    // TDZ starts at beginning of scope
    const sampleFunc = () => console.log(letVariable); // OK

    // Within the TDZ letVariable access throws `ReferenceError`

    let letVariable = 97; // End of TDZ (for letVariable)
    sampleFunc(); // Called outside TDZ!
}
Enter fullscreen mode Exit fullscreen mode

πŸ“Œ Variable Hoisting

Remember that all function and variable declarations are hoisted to the TOP of the scope. Declarations are processed before any code is executed. With this, undeclared variables do not exist until the code assignment is executed. Variable assignment to an undeclared variable implicitly creates it as a global variable when the assignment is executed. That means that any undeclared variable (but assigned) is a global variable.

function demo() {
   globalVar = 34;
   var functionScopedVar = 78;
}

demo();
console.log(globalVar); // Output: 34
console.log(functionScopedVar) // throws a ReferenceError
Enter fullscreen mode Exit fullscreen mode

This is why it is always good to declare variables regardless of whether they are of function or global scope.

πŸ“Œ ES5 Strict Mode

Introduced in EcmaScript 5, strict mode is a way to opt in to a restricted variant of JS. Strict mode make several changes to normal JS semantics

  1. Eliminates silent errors by throwing them
  2. Prohibits syntax that might be defined in future version of ES
  3. Fix mistakes that make JS engines perform optimizations

With regards to hoisting, using strict mode will not tolerate the use of variables before they are declared.

πŸ“Œ Function Hoisting

JS functions can be declarations or expressions.

Function declarations are hoisted completely to the top. That is why a function can be invoked even before it is declared.

amIHoisted(); // Output: "Yes I am."
function amIHoisted() {
   console.log("Yes I am.")
}
Enter fullscreen mode Exit fullscreen mode

Function expressions, are NOT hoisted. This is because of the precedence order of JS functions and variables. The snippet below would throw a TypeError because the hoisted variable amIHoisted is treated as a variable, not a function.

amIHoisted(); //Output: "TypeError: expression is not a function
var amIHoisted = function() {
   console.log("No I am not.");
}
Enter fullscreen mode Exit fullscreen mode

The code execution of the code above would somehow look like this

var amIHoisted; // undefined
amIHoisted(); 
/*Function is invoked, but from the interpreter's perspective it is not a function. 
Thus would throw a type error
*/
amIHoisted = function() {
   console.log("No I am not.");
}
/*The variable is assigned as a function late. It was already invoked before the assignment.*/
Enter fullscreen mode Exit fullscreen mode

πŸ“Œ Hoisting Order of Precedence

  • Variable assignment takes precedence over function declaration. The type of amIABoolean would be a boolean because the variable is assigned to a value true.
var amIABoolean = true;

function amIABoolean() {
  console.log("No.")
}

console.log(typeof amIABoolean); // Output: boolean
Enter fullscreen mode Exit fullscreen mode
  • Function declarations take precedent over variable declarations. From the snippet below, the type of amIAFunction would be a function because on the first line, the variable is only declared, not assigned. Since function declarations takes precedence, it is resolved to type function.
var amIAFunction;

function amIAFunction() {
  console.log("Yes.")
}

console.log(typeof amIAFunction); // Output: function
Enter fullscreen mode Exit fullscreen mode

Conclusion

Hoisting in JS is the compiler splitting variable declaration and initialization and moving only the declarations to the top of the code. So even though the functions and variables are called/used before they are written, the code still works. This happens because of how context execution works in Javascript.

Note that only declarations are hoisted, not initializations. For variables declared with the var keyword, the default value would be undefined. For let variables, until the line in which they are initialized is executed, any code that access them, will throw an exception. These variables are said to be in a "temporal dead zone" (TDZ) from the start of the block until the initialization has completed. Accessing unintialized let variables would result to a ReferenceError.

That is it for the basics of JS hoisting, my fifth WIL(What I Learned) dev post πŸ˜„.

As always, cheers to lifelong learning 🍷!

[REFERENCES]

  1. MDN Web Docs : Hoisting
  2. Understanding Hoisting in Javascript
  3. Javascript Context Execution
  4. Temporal Dead Zone
  5. Strict Mode

Discussion (14)

Collapse
lukeshiru profile image
LUKESHIRU

Actually great post! Hoisting is one of those things that are part of JS and are super confusing for new folks because is not intuitive at all, so posts like this make it super clear.

Thanks for putting it together! ✨

Collapse
pat_the99 profile image
Patricia Nicole Author

Thank you, I am happy to hear that πŸ™‚

Collapse
dominiccronin profile image
Dominic Cronin

Great to see this article. It's an area that confuses a lot of people, and it's not too hard once you know about it.

I found it a bit distracting that you keep referring to the compiler instead of the interpreter. It's not a big thing, but it got in the way of my being able to just follow the flow of your article.

Collapse
petertorres profile image
Pete Torres

Great article!

Collapse
umavijay172 profile image
umamaheshwari v

Hii please provide articles of angular for beginners.
Thanku

Collapse
hmarat profile image
hmarat

Thank you. Amazing post :)

Collapse
grajghimire profile image
Ghimire Gaurav Raj

I clicked on this thinking Hosting.. 🀣

Collapse
vcfvct profile image
leon

Nice material for refreshing the memory of hoisting πŸ™‚

Collapse
ben profile image
Ben Halpern

Nice post

Collapse
ifierygod profile image
Goran Kortjie

Thank you for sharing

Collapse
aidenybai profile image
Aiden Bai

Nice! Really well written article and I enjoyed the illustrations a lot. Good to see that you're learning and sharing with others

Collapse
z00md profile image
z00md

Good article about hoisting. Almost covered all aspects. But I do have one question. Why hositing exists? Whats the purpose of having it in javascript?

Collapse
gravy17 profile image
Tofi Agbaje

Direct and Expressive, Beautifully written- great read!

Collapse
darkcode01 profile image
Jose M. Segura Polanco

good post!