Prerequisite: Understanding Javascript Scoping, Javascript Hoisting
In JavaScript, there are two types of scopes
- Global Scope: Scope outside the outermost function attached to the window.
- Local Scope: Inside the function being executed.
Hoisting: It is a concept that enables us to extract values of variables and functions even before initializing/assigning value without getting errors and this is happening due to the 1st phase (memory creation phase) of the Execution Context.
Do you know what value will be printed on the console when the following piece of code will be executed?
JavaScript
<script>
var x = 10;
function test()
{
var x = 20;
}
test();
console.log(x);
</script>
Output:
10
If your answer is 10, then you are right. Here the variable 'x' declared (and of course initialized) outside the function 'test' has a global scope and that's why it is accessible anywhere in this scope globally. However, the one declared and initialized inside the 'test' function can be accessed only inside that function.Â
So the below code snippet will print 20 on the console upon execution.
JavaScript
<script>
var x = 10;
function test()
{
var x = 20;
console.log(x);
}
test();
</script>
Output:
20
JavaScript
<script>
var x = 10;
function test()
{
if (x > 20) {
var x = 50;
}
console.log(x);
}
test();
</script>
Output:
undefined
If you have guessed 10 following the scoping logic discussed in the previous examples, then you are unfortunate as 10 is not the correct answer. This time it will print 'undefined' on the console. This is because of the combined effect of variable scoping and variable hoisting.Â
Scoping in JavaScript: Let us first understand scoping. The scope is a region of the program where a variable can be accessed. In other words, scope determines the accessibility/visibility of a variable. Since JavaScript looks like a C-family language, it is very obvious to think that scoping in JavaScript is similar to that in most of the back-end programming languages like C, C++, or Java.
Let's consider the following code snippet written in C:Â
C
#include<stdio.h>
void doSomething();
int main() {
doSomething();
return 0;
}
// This function examplifies
// the block-level-scope in C language
void doSomething() {
int x = 10;
printf("%d\n", x);
if (x == 10) {
int x = 20;
printf("%d\n", x);
}
printf("%d\n", x);
}
Output :
10
20
10
Here the output is so, because C, as well as the rest of the C-family, has a block-level-scope. Whenever the control enters into a block, such as an if-block or a loop (e.g., for, while, or do-while), the language allows new variables to be declared that can be visible within that block only. Here the variables declared in the inner scope do not affect the value of the variables declared in the outer scope. But this is not the case in JavaScript.
Example: Let's consider the following JavaScript example.
JavaScript
<script>
var x = 10;
console.log(x);
if (true) {
var x = 20;
console.log(x);
}
console.log(x);
</script>
Output :Â Â
10
20
20
Unlike the previous code, written in C, here the last output changes depending on the value assigned to the variable 'x' inside the if-block. This is because JavaScript does not have block-level scope. It has a function-level-scope. That means blocks such as if-statements and loops do not create new scopes in JavaScript. Rather a new scope is created only when a function is defined. This sometimes creates confusion for developers who are working with C, C++, C# or Java-like languages. Luckily, JavaScript allows function definitions to go inside any block. For example, we can write the above code by implementing an IIFE (Immediately Invoked Function Expression) inside the if-block that will result in a different output.
JavaScript
<script>
var x = 10;
console.log(x);
if (true) {
(function() {
var x = 20;
console.log(x);
})();
}
console.log(x);
</script>
Output :Â Â
10
20
10
In this case, when the control enters into the anonymous function defined and invoked inside the if-block, it creates a new scope. The variable 'x' declared inside this scope does not affect the value of the variable 'x' declared in the outer scope. Though this is a quite flexible way for creating temporary scopes wherever required, it is not qualified as a good coding style. Hence, to keep things simple ES6 has introduced two new keywords- 'let' and 'const', to declare block-scoped variables. When a variable is declared using 'const' or 'let', it is visible only inside the particular block in which it is declared. For example:
JavaScript
<script>
var x = 10;
console.log(x);
function test()
{
var x = 20;
console.log(x);
if (x > 10) {
let x = 30;
console.log(x);
}
console.log(x);
}
test();
console.log(x);
</script>
Output :Â Â
10
20
30
20
10
In this case, when the test() function is invoked the console.log() statement written inside the if-block prints 30 on the console, whereas the one that follows the if-block inside test() function prints 20 on the console. This means that the variable 'x' declared and defined using let keyword has no effect on the value of the variable 'x' declared outside its scope, i.e. the if-block. The keyword 'const' also operates in a similar manner. The only difference between 'let' and 'const' is- const is a signal that the identifier won’t be reassigned (The reason that the word 'identifier' is preferred here over 'variable' is that 'const' is used to declare the identifier, it is no longer a variable. It is a constant.), whereas let is a signal that the variable may be reassigned.
Now let's go back to the following example provided previously in this article.
JavaScript
<script>
var x = 10;
function test()
{
if (x > 20) {
var x = 50;
}
console.log(x);
}
test();
</script>
Output :Â Â
undefined
We have already discussed why in this example the console.log() statement did not print 10 on the console. Since JavaScript (especially the var keyword) has function-level-scope, the variable 'x' declared inside the if-block is visible throughout the function test(). So when the console.log() statement executes, it tries to print the value of the inner 'x' rather than the value of the one declared outside the function definition. Now the question is if this code snippet prints the value of the inner 'x' then why is it printing 'undefined'? Here the inner 'x' is both declared and defined inside the if-block which is evaluated to false and in JavaScript an attempt to access a variable before its declaration results in a ReferenceError. Then how come the variable is even getting declared inside the function allowing it to be executed without any error? Does the function-level-scope make the conditional statements, like if-else, ineffective and if so, why is not it printing 50, the actual value of 'x' declared inside the function? All these questions have a single answer- hoisting.Â
Hoisting in Javascript: It is JavaScript's default behavior of moving declarations to the top of their containing scope. When a JavaScript code is interpreted, the interpreter invisibly moves (hoist) all the variable and function declarations to the top of the scope they are declared in. However, the location of their definition/initialization/instantiation remains unaffected. For example, the above code snippet will be interpreted as the following before execution.
JavaScript
<script>
var x;
x = 10;
function test()
{
var x;
if (x > 20) {
x = 50;
}
console.log(x);
}
test();
</script>
Output:
undefined
In this case, since no value is assigned to the variable 'x' declared at the top of the test() function, JavaScript automatically assigns the value 'undefined' to it. Since the if-condition is evaluated to false, the 'console.log()' statement prints 'undefined' on the console.
Now let's look at another example:
JavaScript
<script>
function test()
{
if (false) {
var x = 50;
}
console.log(x);
console.log(y);
var y = 100;
console.log(y);
}
test();
</script>
Output:Â Â
undefined
undefined
100
This code is interpreted as the following.
JavaScript
<script>
function test()
{
var x, y;
if (false) {
x = 50;
}
console.log(x);
console.log(y);
y = 100;
console.log(y);
}
test();
</script>
Output:
undefined
undefined
100
In the first line of the function body where 'x' and 'y' are declared in the interpreted code, JavaScript assigns 'undefined' to both variables. Since the if-condition is evaluated to false, the first two 'console.log()' statements print 'undefined' on the console. However, the last statement prints 100 as it is assigned to 'y' before the final 'console.log()' executes.
Now the important point here is, this variable hoisting mechanism works only for variables declared using var keyword. It does not work for variables or identifiers declared using let and const keywords respectively. Let's consider the following example.Â
JavaScript
<script>
function test()
{
if (false) {
let x = 50;
}
console.log(x);
console.log(y);
let y = 100;
console.log(y);
}
test();
</script>
Output:Â
ReferenceError: x is not defined
Identifiers declared using let or const are not at all hoisted. This makes them inaccessible before their declaration in the original source code.
Now a question may come- what is the best practice to declare variables and constants in JavaScript keeping variable hoisting in mind, so that it will never make our code return an unexpected result?
Here is the answer.Â
- All variables and constants that need to be visible within the scope of a particular function should be declared using 'var' and 'const' keywords respectively at the top of that function.Â
- Inside blocks (conditional statements or loops) variables and constants should be declared on the top of the block using 'let' and 'const' respectively.Â
- If in a particular scope multiple variables or constants need to be declared, then declare them in one go by using a single 'var' or 'let' or 'const' keyword with comma separated identifier names,Â
e.g.,Â
var x, y, z; // declaring function-scoped variablesÂ
let a, b, c; // declaring block-scoped variablesÂ
const u, v, w; // declaring block-scoped constantsÂ
Though the last point has nothing to do with the consequences of variable hoisting, it is a better practice to keep it in mind while writing code to keep the code clean.
Like variables, functions are also hoisted in JavaScript. However, how the hoisting will be done depends on the way a function is declared. JavaScript allows developers to define a function in two ways- function declaration and function expression.
Let's consider the following example.
JavaScript
<script>
function test()
{
foo();
bar();
// Function defined
// using function declaration
function foo()
{
console.log('foo');
}
// Function defined
// using function expression
var bar = function() {
console.log('bar');
}
}
test();
</script>
Output:Â
foo
TypeError: bar is not a function
The JavaScript interpreter interprets the above code as follows.Â
JavaScript
<script>
function test()
{
function foo() {}
var bar;
foo();
bar();
// Function defined
// using function declaration
function foo()
{
console.log('foo');
}
// Function defined
// using function expression
bar = function() {
console.log('bar');
}
}
test();
</script>
Output:
foo
TypeError: bar is not a function
Since function foo() is defined using function declaration, the JavaScript interpreter moves its declaration to the top of its container scope, i.e., the body of test(), leaving its definition part behind. The definition is dynamically assigned when foo() is invoked.Â
This leads to the execution of the body of foo() function when the function is called. On the other hand, defining a function using function expression is nothing other than variable initialization where the function is treated as the value to be assigned to the variable. Therefore it follows the same hoisting rule which is applicable to variable declarations. Here the declaration of the variable 'bar' is moved to the top of its container scope while the place of assigning the function to it remains unchanged. JavaScript cannot interpret a variable as a function until it is assigned a value that is actually a function. This is why trying to execute the statement bar(); before 'bar' is defined results in a TypeError.
Though the decision regarding which way one should use to define a function solely depends on developer's own choice, it is better to keep the following things in mind.Â
- Define all the functions using function declaration method on the top of their container scope. This not only keeps the code clean but also ensures that all the functions are declared and defined before they are invoked.Â
- If you must have to define a function using function expression method, then make sure that it is defined before it is invoked in the code. This will eliminate the chance of unexpected outputs or errors that may result due to hoisting.
Similar Reads
JavaScript Hoisting
Hoisting refers to the behaviour where JavaScript moves the declarations of variables, functions, and classes to the top of their scope during the compilation phase. This can sometimes lead to surprising results, especially when using var, let, const, or function expressions.Hoisting applies to vari
6 min read
JavaScript Optional Chaining
JavaScript Optional chaining is a new feature introduced in ES2020 that simplifies the process of accessing properties nested within objects, especially when dealing with potentially null or undefined values. It provides a concise syntax for accessing properties deep within nested objects without en
2 min read
AND(&) Bitwise Operator in JavaScript
JavaScript Bitwise AND(&) operator is used to compare two operands by performing an AND operation on the individual bits and returning 1 only if both the bits are one. The AND(&) Operator has a lot of real-world applications and the most famous one is to check whether a number is even or odd
2 min read
Function Overloading in JavaScript
Function Overloading is a feature found in many object-oriented programming languages, where multiple functions can share the same name but differ in the number or type of parameters. While languages like C++ and Java natively support function overloading, JavaScript does not support this feature di
4 min read
How does JavaScript Hoisting work internally ?
In simple terms, hoisting in JavaScript refers to the fact that variables can be used before they're declared. The declared variables are somehow virtually moved to the top of the scope. However, hoisting applies only to variable declarations and not to variable initialization. While almost anyone
4 min read
Array of functions in JavaScript
Given an array containing functions and the task is to access its element in different ways using JavaScript. Approach: Declare an array of functions.The array of functions works with indexes like an array function. Example 1: In this example, the function call is initiated from the element of the a
2 min read
Atomics.and() In JavaScript
What is Atomics? Atomics is an object in JavaScript that provides the ability to perform atomic operations as static methods.Just like the Math object in JavaScript all the properties and methods of Atomics are also static.Atomics are used with SharedArrayBuffer(generic fixed-length binary data buf
3 min read
Code Golfing in JavaScript
Code Golf in JavaScript refers to attempting a problem to solve using the least amount of characters possible. Like in Golf, the low score wins, the fewest amount of characters "wins". JavaScript is a fantastic language for code golfing due to backward compatibility, quirks, it is being a high-level
4 min read
Interesting Facts About JavaScript Strings
Let's see some interesting facts about JavaScript strings that can help you become an efficient programmer.Strings are ImmutableIn JavaScript, strings are immutable, which means once you create a string, you cannot change its characters individually. Any operation that appears to modify a string, li
2 min read
String blink() Method in Javascript
In Javascript, the blink() method is a string method that is used to display a string inside the blink tag. Syntax: string.blink() Note: This method has been DEPRECATED and is no longer recommended. Parameter Values: This method does not accept any parameters. Return Values: It returns a string valu
1 min read