tl;dr: During Creation Phase memory is allocated to variables and functions, because of this during Execution Phase variables and functions are available even before they are initialized in the code.
Hoisting is not a feature, it is actually a side effect of javascript’s default behaviour.
To understand hoisting and why it occurs we need to understand what the JS engine does when it is given a piece of code to execute.
The JS engine takes 2 phases to run a given piece of code. These two phases are:
- Creation phase
- Execution phase
Hoisting is a result of what happens in the Creation Phase.
Creation Phase
Creation Phase is the phase in which the JS engine goes through the code to allocate space in the memory for all the variables and functions, so that they are available during the Execution Phase when they are referenced in the code.
This memory allocation to variables and functions before code execution is the reason for hoisting.
Variables are given the space in memory for their declarations only but for functions the entire definition is stored in the memory in the creation phase. This is why variables are said to be partially hoisted and functions are said to be completely hoisted.
Example:
console.log(“Output Firstname:”, firstname );
console.log(“Output Age:”, printAge() );
var firstname='John';
function printAge(){
var age = 40;
return age;
}
When we give this piece of code to the JS engine, it will first run through the code and allocate memory for variable firstname and function printAge, even before executing the first line : console.log(firstname), as we can see in the video below, if we add a breakpoint at the first line we can see that variable “firstname” and function “printAge” are available in the memory
Since that variable is already in memory, console.log will not throw any error instead display the value of that variable at point of time which is “undefined”. Same will happen for the function printAge, since functions are completely hoisted the function will be run as usual.
Memory allocation for variables vs functions
If we run the above code the output is
As we observe in the above image function printAge ran correctly but for variable firstname we got undefined, why ?
It is so because when memory is allocated for a variable it’s value is not stored in memory until the line of code which contains the variable initialization is executed, but when memory is allocated for a function the entire function definition is stored in memory.
So prior to the execution phase the variable firstname and function printAge are stored in memory as follows:
firstname : undefined
printAge : function(){ var age = 40; return age; }
Now that we are familiar with the Creation Phase, let's discuss what is hoisting.
Hoisting
Hoisting is the default behaviour of variables and functions being available even before their initialization.
As we saw in the examples above variable firstname and function printage both were available even before the execution had reached their definitions.
Limitations of hoisting:
Hoisting is limited to variables defined with the keyword var and functions defined with the keyword function.
Hoisting does not apply to “const”, “let” and anonymous functions or arrow functions; these features were introduced in javascript in ES6 or ES2015.
Pros and Cons:
The only advantage of hoisting is that functions are available for execution even before their definition.
The disadvantage of hoisting is that it leads to unpredictable code, which could be a very serious problem for any application.
Best Practices:
How do we avoid hoisting so that our code is not unpredictable in any case?
The answer is, by taking advantage of the limitations of hoisting.
Define variables by using:
- “const” when you know the value of the variable will remain the same and not change throughout the course your application,
Example:
console.log(firstname) // this will throw a reference error
const firstname = "John" // -> const variable
- “let” when you need to change the value of the variable
Example:
console.log(firstname) // this will throw a reference error
let firstname = "John" // let variable
Define functions by using:
- Functions Expression.
Example:
console.log(printAge()) // this will throw a reference error
const printAge = function () {
// -> function expression
var age = 40
return age
}
- Arrow functions.
Example:
console.log(printAge()) // this will throw a reference error
const printAge = () => {
// arrow function
var age = 40
return age
}
This happens because variables defined with let and const and functions that are anonymous or arrow function are not hoisted by JS engine hence this makes the code more predictable and easy to debug.
Stay tuned for more soon...
Top comments (4)
Need to be careful when talking about anonymous functions. The 'printAge' function is not anonymous. Try
console.log(printAge.name)
- you will get 'printAge'. The act of assigning an anonymous function (as in the function expression above) to a variable gives it a name.The fact that it is not hoisted is to do with the way it's assigned - in this case using
const
- the fact that it is anonymous, an arrow function, or otherwise - is irrelevant.Thanks
Awesome explanation, This also explain TDZ
Thanks