What is scoping and hoisting in JavaScript? Levels of scoping in JS
In the previous blog on JavaScript Functions, you learned about the basics of what functions help with. Functions help you perform the same task again and again for a given input.
Let’s learn three major concepts of scoping, hoisting, and arrow functions in JavaScript in this blog. Buckle up! It is going to be lengthy but interesting.
Scoping in JavaScript
There are multiple ways to do scoping in JavaScript. Let’s look at a few ways:
Scoping with let in JavaScript
You have learned about the const
and let
variables in JS already. To refresh your memory, check out this blog. What would happen if you define a const
or let
variable inside a function?
function greeting(name) {
let greet = "Hello"
console.log(`${greet}! I am ${name}`)
}
greeting("Rishabh")
Code language: JavaScript (javascript)
The output of this code will be:
Hello! I am Rishabh
Code language: plaintext (plaintext)
What do you think will be the output of the code when I do this:
function greeting(name) {
let greet = "Hello"
console.log(`${greet}! I am ${name}`)
}
greeting("Rishabh")
console.log(greet)
Code language: JavaScript (javascript)
Let’s check it out:
It gives a ReferenceError
. What that means is that JavaScript doesn’t know where the greet
variable has been defined and in fact, the error message also reads that greet
is not defined. But it is clearly not true! Or is it?
Let me walk you through what is happening:
As commented, the function starts at line 1 and ends at line 4. Whatever you place inside the {}
of a function is said to come under a function’s scope. As soon as this scope ends at line 4, JavaScript will no longer be able to access any variables defined inside that function, in this case greet
.
The only exception to this is that the function is actually returning the value you are trying to access. So the following would work:
function greeting(name) { // Function starts
let greet = "Hello" // greet variable is defined
console.log(`${greet}! I am ${name}`) // greet variable and name parameter are printed
return greet
} // Function is closed
// Calling the function
let greet = greeting("Rishabh")
// Printing greet
console.log(greet)
Code language: JavaScript (javascript)
Here you are explicitly storing the value the function is returning in a variable outside the scope of the function. Read that again!
Scoping with const in JavaScript
What if I replace let
with const
?
The output still remains the same! You get the same error message. So even variables defined with const
are not available outside the function scope.
This property of let
and const
not being available outside the function scope is called as blocked scoping. The same is applicable for the variables defined inside an if
block. You can watch this 1-minute video for a better understanding:
Any variable outside the if
block or a function
block is said to be globally scoped.
A globally scoped variable like greetOne
can be accessed from anywhere in the code.
JavaScript will first check if a variable is available in the local scope. A local scope is all the code inside the curly braces {}
be it a function or an if-else
block. If it is available, it will use the value from the local scope. Otherwise, it will move to the global scope and see if the value is available. If it is not available even in the global scope, it will give a ReferenceError
.
Experiment with the following code to cement your understanding of scoping with const
and let
:
// Global Scope
let msg = "I'm in the GLOBAL scope"
console.log(msg)
// Block scoped
if (true) {
let msg = "I'm in the IF BLOCK"
console.log(msg)
}
// Function scope
function foo() {
let msg = "I'm in the FUNCTION BLOCK"
console.log(msg)
}
foo()
Code language: JavaScript (javascript)
Now that you fully understand scoping, let us look at hoisting in JavaScript!
Hoisting in JavaScript
Do you think the following code will work?
greet()
function greet() {
console.log("Hello Rishabh!")
}
Code language: JavaScript (javascript)
See carefully, that the function greet
has been called before its definition. Check the output below:
It actually prints Hello Rishabh!
This behaviour of JavaScript is referred to as Function Hoisting!
When the JavaScript interpreter runs the code, it goes through all the function definitions declared with the following syntax:
function functionName() {
// Code to run
}
Code language: JavaScript (javascript)
And it moves this declaration to the global scope!
You can think of JavaScript first declaring the function in line 1, and then calling greet()
.
This means that usually, the sequence in which the code is written in a file does not matter that much. What matters is the order of its execution!
But, what if you want to avoid this behaviour of JavaScript? That’s where arrow functions come in!
Arrow Functions in JavaScript
function multiply(x, y) {
return x * y
}
Code language: JavaScript (javascript)
Let us see the above function multiply as an arrow function:
const multiply = (x, y) => {
return x * y
}
Code language: JavaScript (javascript)
It is just a new way of writing functions! You replace the function
keyword with the const
or let
and then add an equal to symbol and an arrow to the existing code! That is about it.
It surely can’t just be syntactical, otherwise, what’s the use? Well for starters, you can write the above function in a more compact manner!
const multiply = (x, y) => x * y
Code language: JavaScript (javascript)
This will work exactly the same. I have removed the curly braces, an extra line, and the return
statement from the code. And if you have only one parameter and do a simple operation like add a 1 to it or print it to the console, then you can also omit the parenthesis ()
:
let printNum = x => console.log(x)
const addOne = x => x + 1
printNum(5)
addOne(5)
Code language: JavaScript (javascript)
printNum
and addOne
are functions.
Therefore, one advantage is that it reduces the verbosity of code.
Let us pick up the code for calculating the average score from the previous blog.
function average(marks) {
let sumMarks = 0
for (let i = 0; i < marks.length; i++) {
sumMarks += marks[i]
}
let average = sumMarks / marks.length
return average
}
Code language: JavaScript (javascript)
When we convert this to an arrow function, it will look something like this:
const average = marks => {
let sumMarks = 0
for (let i = 0; i < marks.length; i++) {
sumMarks += marks[i]
}
let average = sumMarks / marks.length
return average
}
Code language: JavaScript (javascript)
As you can see, there was almost no reduction in verbosity in this case. So then the question stands, beyond small use cases, why use arrow functions at all?
Try this out:
console.log(multiply(5, 4))
let multiply = (x, y) => {
return x * y
}
Code language: JavaScript (javascript)
In the previous section, we had seen that a function defined with function
keyword ran for a case like the one above. However, this won’t run!
That’s because a function created using the arrow syntax is not hoisted to its higher scope (in this case, the global scope). And so, JavaScript cannot know that a function multiply
has been defined, giving us yet another ReferenceError
.
Thus, using arrow functions will bypass Function Hoisting in JavaScript.
Conclusion
We covered a lot of bases on scoping, hoisting, and arrow functions in JavaScript.
For scoping, you learned about the local and global scopes and the function and block scopes. In hoisting you saw, how the order of code written matters less. Arrow functions helped you reduce verbosity in some cases and bypass hoisting in others!
In the next blog, you will learn more about adding methods to the Object Data Type and the this
keyword in JavaScript. See you then!
Sharing is caring
Did you like what Rishabh Mittal wrote? Thank them for their work by sharing it on social media.
No comments so far
Curious about this topic? Continue your journey with these coding courses: