Interview Preparation Cheat Sheet ๐Ÿ“’

Interview Preparation Cheat Sheet ๐Ÿ“’

Hii ๐Ÿ‘‹ everyone, hope you all are doing good. Today we are going to explore some of the important topics in JavaScript, which will help us to understand behind the scene working of the javascript in a web browser.

  • Execution context
  • Hoisting
  • Scope
  • Call Stack
  • Synchronous and Single-Threaded

How JavaScript Code Gets Executed?

The web browser does not directly understand the high-level javascript code that we write in our applications. Not only in a web browser but in any other programming language the CPU does not understand the high-level code, It first needs to be converted into such a language that the CPU, in our case a web browser can understand - machine code.

While scanning through the HTML file, if the browser finds any javascript code via <script> tag it will send this script to its javascript engine which will then create a special environment for the transformation and execution of the code, this environment is called Execution Context.

The execution context contains the code which is currently running and everything necessary to aid its execution.

There are two kinds of Execution Context in JavaScript:

  • Global Execution Context (GEC)
  • Function Execution Context (FEC)

Global Execution Context (GEC)

Whenever a JavaScript engine encounters a script file, it first scan the whole file and stores all the variables and functions inside different memory allocations in the global scope (space), which will be executing in the execution phase. if there is a variable it will be given a different memory allocation inside the GEC and if there is a function, the whole function declaration will be given a different memory allocation. This scanning of the file and allocating the memory based on the type is known as hoisting.

For every JavaScript file there can be only one GEC.

Function Execution Context (FEC)

Whenever a function is called, the JavaScript engine creates a different type of Execution Context known as a Function Execution Context (FEC) within the GEC to evaluate and execute the code within that function. Since every function call gets its own FEC, there can be more than one FEC in the run-time of a script.

let's understand this with an example.

let a = 10;    // In global scope
function forExample(){
    let b = 20;    // In function scope
    console.log(b);
}
forExample();

So what happens is that during the scanning phase it will store the variable a in a memory allocation and whole function declaration forExample in a different memory allocation, and since the execution phase is not started yet so the value of the variable a will be assigned as undefined.

gec.png

And during the execution phase, a value of 10 will be assigned to a. and a new execution context - functional execution is created which again creates a GEC inside it and the value of the variable b becomes undefined, and during functional execution context of the function, b will be assigned 20.

fec.png

This process of storing variables and function declaration in memory prior to the execution of the code is known as Hoisting.

Hoisting in JavaScript

Function and variable declarations are hoisted in JavaScript. This means that they are stored in the memory of the current execution Context as discussed above and made available within the Execution Context even before the execution of the code begins. This allows functions to be used in the code before they are declared.

Variable Hoisting

In variable hoisting, we can use a variable before it is declared or initialized. But the most important thing is that only variable declarations are hoisted not the initializations. For variables declared using the var keyword the default initialization is undefined and otherwise no initialization. Below is the use case of this scenario.

In this example first we have declared the variable and then initialized it after using it.

console.log(temp);  // Output: undefined, because we are using it before it is actually initialized
var temp;    // Declaration
temp = 10;  // Initialization
console.log(temp);  // Output: 10, because we are using it after initializing it.

The same thing will happen if we declare and initialize the variable in the same line as shown below.

console.log(temp);  // Output: undefined, because we are using it before it is actually initialized
var temp = 10;   // Declaration and Initialization in the same line
console.log(temp);  // Output: 10, because we are using it after initializing it.

If we don't declare the variable and only initialize it, this will give a reference error because the variable is not hoisted in this case.

console.log(temp);  // Reference Error: temp is not defined
temp = 10;  // Initialization

Although the variable is not hoisted in the above case but if we use the variable after its initialization the code will work fine.

temp = 10;  // Initialization
console.log(temp);  // Output: 10

Let and Const Hoisting

Variable declared with the Let and const keywords are also hoisted but unlike var they are not initialized with the default value: undefined. An error will occur when we access any variable declared with the let and const keywords before it is initialized.

console.log(temp); // Reference Error: as the variable is not initialized
let temp = 10; // Initialization

class Hoisting

Classes defined using class declaration are hoisted but are not initialized by default. if we use any class before it is defined will throw a ReferenceError as shown below.

let saini = new Student("saini", 19)   // ReferenceError: Cannot access 'Student' before initialization
console.log(saini .name);
class Student {
    constructor(name, roll){
        this.name = name;
        this.roll = roll;
    }
}

Correct way is to use class after it is declared and defined.

class Student {
    constructor(name, roll){
        this.name = name;
        this.roll = roll;
    }
}
let shiva = new Student("shivanand", 10)
console.log(shiva.name);    // Output: shivanand

Function Hoisting

A function declared using the function keyword will be hoisted by JavaScript and will be made available anywhere in the code irrespective of the position of the function declaration.

iamHoisted();    //hoisted
function iamHoisted(){
    console.log("hoisted");
}
iamHoisted();    //hoisted

However, if we assign a variable a function (functional programming) then in this case the variable declaration is hoisted and the expression (function definition) is its initialization. Therefore the expressions are not evaluated until the relevant line is executed. this is same as variable hoisting.

let temp1 = function (){
    console.log("Am I Hoisted?");    //Am I Hoisted?
}
temp1();

temp2();
let temp2 = function (){
    console.log("Am I Hoisted?");    //ReferenceError: Cannot access 'temp2' before initialization
}

HalfwayThereGIF.gif

So now let's discuss about scope in JavaScript.

Scope

Scope in general terms means where to look for things and what it is that we are looking for. It is the current context of execution in which variables and expressions are made available for use or can be referenced. And if the variable is not available for use it can't be used. Further, a scope can be hierarchically chained in one another so that the child scope can access the parent scope but not vice versa.

Types of scope

  • Global Scope: This is the default scope for all the code running in the JavaScript.
  • Module Scope: The scope for the code running in the module mode.
  • Function Scope: The scope created by the function.
  • Block Scope: The scope created within the curly braces { }.

Function Scope

A function creates the scope so that the variable declared inside the function can't be accessed from outside the function and within other functions as shown below.

function forExample (){
    let temp = 10;    // exclusively for forExample function
    console.log(temp);    //10
}
forExample();
console.log(temp);    //ReferenceError: temp is not defined
function forExample (){
    var temp = 10;  // exclusively for forExample()
    console.log(temp);  //10
}
function forExample2 (){
    console.log(temp);  //ReferenceError: temp is not defined
}
forExample();
forExample2();
console.log(temp);  //ReferenceError: temp is not defined

Scope Chaining and Lexical Scope

The nesting of the scope inside different functions is called as scope chaining.

function parent (){
    var moneyParent = 100;
    console.log(moneyParent);     // 100
    console.log(ageChild);      // ReferenceError: ageChild is not defined
    console.log(ageGrandChild);      // ReferenceError: ageGrandChild is not defined
    function child (){
        var ageChild = 30;
        var moneyChild = 50;
        console.log(moneyParent);     // 100
        console.log(moneyChild);     // 50
        console.log(ageChild);      // 30
        console.log(ageGrandChild);      // ReferenceError: ageGrandChild is not defined
        function grandChild(){
            var ageGrandChild = 8;
            var moneyGrandChild = 1;
            console.log(ageChild);      // 30
            console.log(ageGrandChild);      // 8
            console.log(moneyParent);     // 100
            console.log(moneyChild);     // 50
            console.log(moneyGrandChild);     // 1
        }
        grandChild();
    }
    child();
}
parent();

In the above example, we can see that the parent function variables and child function variables can be accessed inside the grandChild function, but the reverse is not possible this is called scope chaining. and we can also say that grandChild is in lexical scope with child, and child is in lexical scope with the parent, and parent is in lexical scope with global which is called global scope.

scope chaining.png

Block Scope

The scope created with the pair of curly braces { } while using the let and const keywords.

{
  var temp = 10;
}
console.log(temp);    // 10
{
  const temp = 10;
}
console.log(temp); // ReferenceError: temp is not defined
{
  let temp = 10;
}
console.log(temp); // ReferenceError: temp is not defined

AlmostThereGIF.gif

Call Stack

A call stack is almost like a mechanism by which the interpreter of the web browser keeps track of its place in the JavaScript code that makes multiple calls or has functions that call another function. As what function is currently being run and how many functions calls it is making from that function and so on. The working is like this:

  • First, when the script calls the function the interpreter places it on the top of the GEC (Global Execution Context) and then start executing the function.

  • During the execution of the first function if this function is making another function calls from within, the call stack add that call further up in the call stack (above the call of first function) and start executing it.

  • When the execution of the current function is done, the call stack removes it from the stack and returns the code execution flow to the point where it left off in the last code listing.

  • After returning to the point where it left off in the last code listing, it executes the remaining code and returns the execution flow to GEC.

  • Finally call stack removes that function also from the call stack and GEC takes over the control.

function firstFunction(){
    // Some code
    console.log("Inside firstFunction");
    secondFunction();
    // Some code
    console.log("Again Inside firstFunction");
}

firstFunction();

function secondFunction(){
    console.log("Inside Second function");
}

// Output:
// Inside firstFunction
// Inside Second function
// Again Inside firstFunction

Below diagram will help in understanding this.

call stack.png

Last one โœŒ๏ธ

TheCountdownBeginsGIF.gif

Synchronous and Single-Threaded

Synchronous simply means that the script will get executed line by line after completing each task (sequence). And if one task is taking more time to complete the task it will wait for the task to get done and then move to the next task.

Single-threaded means that the JavaScript engine only has one call stack to do all the tasks. Let's understand this by an example

Case-1

console.log("1");
for(let i = 1; i<10000; i++){
// a loop that takes 5 seconds to complete
}
console.log("2");

Output:
1
2 //after 5 seconds

Case-2

console.log("1");
setTimeout(() =>{
    // fetching web api..
    console.log("Web api..")
},5000)
console.log("2");

Output:
1
2
Web api..  // after 5 seconds

If we have something like this then in case-1 the 2 will only be printed as soon as the task of the for loop is completed i.e after 5 seconds, this behavior of executing code line by line after completion of the task is called synchronous behavior. Whereas the code in case-2, the output 2 will be printed instantaneously because setTimeout is a browser feature that is deferred and the function inside it is called after five seconds has been completed. Therefore certain browser features and third-party libraries that access the network are async in nature so that in the meantime the request (task) is getting fulfilled our JS engines have some task to perform.

That's all for now, let's meet again with some other article until then keep learning.


ย