Understanding Hoisting in JavaScript.
People complain that there are lots of traps while writing JavaScript code.
Developers are smart people and they’re constantly trying to create tools that make JavaScript debugging easy.
This post clarifies the most basic confusions about how JavaScript uses variables and functions you provide it.
Let’s take a look at this code.
var x = 12;
(function () {
console.log(x);
var x = 13;
}());//undefined
You might want to think the answer above would be 13 or 12. Actually, it’s undefined
. I’ll tell you why, let’s just dive in.
Scope in JavaScript.
There are three levels of scope in JavaScript.
- Global scope: Pretty much every variable you declare on your window object. You can use it anywhere in your program. In JavaScript, any variable you declare without a
let,
var
, orconst
goes to the global scope.
var x = 10;
function changeX(){
x = 12; //this changes the value of x in the global scope.
}
changeX();
console.log(x);//12
2) Function scope: When you define a function, all variables from the global scope are available to it. It also has access to all variables declared inside of it, so far they are not declared inside another function.
In JavaScript, only functions create new scope. At least before our next point.
3) Block scope: Introduced in the latest version of JavaScript (ES6). Variables are only available in block code (within curly braces {}). Meaning that we can create new scope without functions. But we must use the let
keyword to declare those variables. In fact, you should also use let to declare all of your variables. let’s see this example.
let x = 10;
for(let x = 0; x < 2; x++){
console.log(x);
}
console.log(x);// 1
// 2
// 10
In this example, x
in the for loop doesn’t tamper with the value of x
in the global scope. The let
keyword can be traced down to Mathematics expressions where working with a new mathematical idea would require changing variables.
I remember saying let the value of X be bla bla
. I’m sure you do too…
How JavaScript Hoists.
JavaScript runs code in two steps.
Declaration phase:
All variables and functions declared within a scope are mapped to their identifiers so they can be easily found during execution proper.
Initializations and assignments are not hoisted in JavaScript. Only declarations are. That’s very important to note.
Execution phase:
Identifiers to all declared variables and functions are available and program is run line by line.
Let’s go back to our first example
var x = 12;
(function () {
console.log(x);
var x = 13;
}());//undefined
One would expect that the console.log(x)
statement should use x
from the global scope or even use x
from the inner scope. But JavaScript proofs us wrong
This all boils down to the fact that JavaScript declares all everything in the background.
When we do var x = ++x
, JavaScript breaks that to var x
and x = ++x
and moves (hoists) only the declaration to the top of the function. The code above is equivalent to
var x = 12;
(function () {
var x;
console.log(x);
x = ++x;
}());//undefined
So when console.log(x)
runs, our Immediately Invoked Function Expression (IIFE) only has x
declared. We can confirm this with this code below
var x = 12;
(function () {
console.log(x);
var x = 13;
console.log(x);
}());//undefined
Now, the second console.log(x)
statement runs with the value of x
as 13.
Functions
Function declarations are hoisted also. But we can’t say for sure. Let’s look at this example.
logSomething();function logSomething(){
console.log(11);
}// 11
Straight forward right? Still JavaScript hoisting here. We can call the logSomething()
function before declaring
it. This takes us to our next point.
Only declarations are hoisted
This is not something new in this post, let me show you how this applies to functions.
logSomething();var logSomething = function logSomething(){
console.log(11);
}//uncaught TypeError:logSomething is not a function.
This shouldn’t suprise you if you get the difference between function declarations and expressions.
In our above example, the logSomething
variable is hoisted to the top of the program scope. When execution begins, the program only knows logSomething
as a declared variable, the function expresion body is not hoisted.
The code above is equivalent to this:
var logSomething;logSomething();logSomething = function logSomething(){
console.log(11);
}//uncaught TypeError:logSomething is not a function.
Preventing scope problems
We’re used to the JavaScript environment. Sure we can avoid some of these little potholes by…
- Declaring all variables at the top of our functions.
- Avoid using
var
. - Make use of one
let
orconst
per function.
let one, two, three, four;vs let one;
let ...
3. Always declare and define all functions before using them.
That would be all for now.
And sure, those tips aren’t enough to prevent scope related problems. I’ll love to hear yours.
If you think this post was helpful, you might want to share with friends and colleagues. They’ll find them helpful too.
I would love some claps on this posts too. It helps me know how helpful these posts are.