Scope, Closure & Hoisting In JavaScriptšŸ˜

Introduction

In this article, we are going to learn about Scope, Closure, and Hoisting.

When we are building a real-time application we will be using a lot of functions and a lot of variables and these variables will get used by multiple functions. What if one variable value will be overridden by another function.

In other languages like C#, java Scope is block-specific but in JavaScript scopes are function-specific and variables defined inside a function are in the local scope, and variables defined outside of a function are within the global scope.

In Javascript language, there are two types of scopes.

Global Scope

Let’s learn about Global Scope.

Any variable declared outside all function, independently that’s a part of global scope.

var x = "cinnabonsticks";
function dessert() {
    console.log(x); // prints "cinnabonsticks"
}
dessert();

Local Scope

Any variable declared independently inside a function has its own scope and that is not affected by the value of the variable which is the part of global scope. A variable with the same name might exist both inside and outside of a function, always remember the scope of that variable that is present inside the function is independent and unaffected by the value of that variable that is present outside.

var x = "cinnabonsticks";
function dessert() {
    var x = "cinnabonchips";
    console.log(x); // prints "cinnabonchips"
}
dessert();

Block Scope

Let's have a look at the working of the block scope.

We already learned in Javascript to create variables by using the “var” keyword. There is another way of creating a variable by using a keyword called “let”.

When we create a variable with a let keyword that statement declares a block scope local variable. The "let" keyword enables us to declare and assign a value to a variable ONLY for that given scope/function/block. The value assigned to it is no longer accessible outside the block.

The “let” keyword has been introduced in the newer version of JavaScript and eventually it was not present in the older versions.

var x = "cinnabonsticks";
function dessert() {
    if (true) {
        let x = "cinnabonchips";
        console.log(x); // prints "cinnabonchips" because in this scope, x has value "doughnut"
    }
    console.log(x); // prints "cinnabonsticks" because "let" keyword uses block scoping. x assumes its original value which is "cinnabonsticks"
}
dessert();

The most important thing to remember while dealing with the scope is by using var and let keyword, whenever we used the let keyword scope becomes a block and the value of that variable declared is only valid inside that block.

Let's look at the example,

var a = 1;
function seven() {
    if (true) {
        var a = 7;
    }
    console.log(a);
}

When you call the function seven(), you will find that prints a value of 7. While resolving a variable, JavaScript starts at the innermost scope and searches outwards. So, when the console.log(a) is encountered, the compiler starts at the innermost scope and searches outwards. In the innermost scope (the mandatory "if" block), a has a value of 7. Thus, 7 is printed on the console.

Let's try it by using the let keyword,

var a = 1;
function seven() {
    if (true) {
        let a = 7;
    }
    console.log(a);
}

As we learned the keyword "let" always has block scope, meaning that outside the "if" block, the "let a = 7" statement is no longer valid. Hence, when you print the value of “a” outside this block, it prints 1!

Global & Local Scope

Let's learn about Global and Local Scope together. Have a look at the example,

var x = 1;
function foo() {
    console.log(x);
    var x = 2;
    console.log(x);
};
foo();

Think about the output. Will it be 1 and 2, or will it be undefined and 2?

This will print output undefined and 2 rather than 1 and 2 since JavaScript always moves variable declarations declared with var to the top of the scope, let's create a code that how the interpreter executes the above code.

var x = 1;

function() {
    var x;
    console.log(x);
    x = 2;
    console.log(x);
};

The above example shows how the interpreter executes this in JavaScript.

How JavaScript always moves variable declarations declared with var to the top of the scope. This concept is something called Hoisting.

Closure

Closures are the most important topic in JavaScript.

Let us try to understand what are closures and how they work.

Look at a code snippet,

function greet(fname) {
    var greeting = "Welcome! " + fname;
    var message = function() {
        console.log(greeting);
    }
    return message;
}
var sayHello = greet("Gurpreet");
sayHello();

In the above code snippet inside the function “greet(fname)”, the variable message stores a function that prints the variable "greeting". Note that the variable "greeting", throughout the function “greet”, has a local scope. Meaning that ideally, the variable "greeting" should not exist outside the function greet().

Now, look at the last return statement in “greet”. It returns "message" which is a variable that stores an anonymous function inside it.

Outside the function there is the variable “sayHello” holds the value returned by function greet("Gurpreet"). Now, you would think the local variable "greeting" should not be accessible once the "return message" statement is executed inside the greet(fname) function.

While calling the sayHello() function, it prints "Welcome! Gurpreet" and behaves as if the local variable "greeting = Welcome! Gurpreet" still exists outside the function “greet”! This happens because of a concept we call closure.

The above code has a closure because of the anonymous function function() { console.log(greeting); } is declared inside another function, greet(fname).

In JavaScript, whenever you declare another function inside an already existing function, you are creating a closure. Once you declare a function inside another function, then the local variables and parameters of the external function can remain accessible by the internal function EVEN AFTER the inner function has returned. This can be seen here because we call the function sayHello() EVEN AFTER we have returned from the greet(fname). sayHello() actually references to the variable "greeting", which was a local variable of the function greet(fname).

Let's have a look simple example, see this code below,

var x = "cinnabonStick";
function dessert() {
    var x = "cinnabonchips";
    function print() {
        console.log(x); // prints "cinnabonchips"
    };
    print();
};
dessert();

This is also another simple case of closure. When we call the “dessert()” function in the last line, the original value of variable x is “cinnabonsticks”. Now we see that another function “print()” is nested inside the “dessert()” function.

Inside the “dessert()” function, the value of variable x has been modified to “cinnabonchips” and even after the function “print()” has ended/returned inside the “dessert()” function, yet it remembers the value of x which is “cinnabonchips” inside the “dessert()” function.

To be specific, the print() function can refer to “x” declared in the dessert() function and remembers its value because

  • The general idea of closure is that closure is created whenever you use a function inside another function. Also, an inner function Closure in JavaScript keeps a copy of the state of its local variables and the variables that it refers to in the parent function.
  • On closure, the inner function (or print() in this example) can refer to and remember the values of the variables and parameters declared in its parent function (or dessert() in this example)
  • Therefore, that’s why we can call console.log(x), and the value “cinnabonchips” is printed out to the console

Hoisting

It is a process through which variable and function declarations are virtually moved to the top of your code. In JavaScript will process variables declared with “var” before any other code is processed. 

function printAge() {
    age = 19;
    console.log("My age is " + age);
    var age;
}

Work perfectly fine, even though the declaration of the variable "age" comes after the point when the variable "age" is used. In variable hoisting, JavaScript will interpret the above code as follows,

function printAge() {
    var age; // variable declarations are moved to the top of the code due to hoisting
    age = 19;
    console.log("My age is " + age);
}

An most important here that in Hoisting is that JavaScript only hoists declarations, not initializations. If a variable is declared and initialized after using it, the value will be undefined.

For example,

console.log(num); // Returns undefined
var num;
num = 6;

In the above example, Javascript will move the line "var num;" to the above console.log(num) because of variable hoisting. More specifically, the Javascript will interpret the code in the above example as,

var num;
console.log(num); // Returns undefined
num = 6;

Therefore, even though num is declared as a variable, no value has yet been assigned to num and hence it returns “undefined”.

If you declare the variable after use but initialize it beforehand, it will return the value,

num = 6;
console.log(num); // returns 6
var num;

That's all about Scope, Closure, and Hoisting.

Happy Reading