Hi guys, in this article we will be learning about the most common and important questions that are asked in a JavaScript Interview.
Let's start with the topics.
Single Thread Programming Language
We have heard that JavaScript is a single-threaded language. Now the question arises what is Single Threaded ?
JavaScript is implemented by JavaScript engines like:
- V8 Engine for Google Chrome
- Spider Monkey for FireFox
- JavaScriptCore for Safari
These engines are made up of two components:
- Heap Memory
- Execution Context Stack
Heap Memory:
In heap memory Function references, objects, and all the other components like variables etc are stored, Heap is a space inside the PC where all this data is stored.
Call Stack:
Now comes the call stack also know as Execution Context Stack, as we have seen in the execution context stack, first Global execution context gets created, then when the engine reads any function in the code it creates the Functional execution context above the global execution context and when this function gets completed it pops off the global execution context.
Stack follows LIFO - Last In First Out. And in JavaScript we have only one call stack in the engine thus, the function does not have any other means to get executed, it has to follow LIFO.
Thus we can say that JavaScript is a single-threaded language and it is synchronous in nature.
Scope
There are two types of scope:
- Global Scope
- Local Scope
Whenever you define something, like a variable, you want it to be inside certain boundaries because sometimes it does not make sense to have a variable outside a function. For example, if you have a function that calculates the area of a rectangle using two inputs height and width, and returns the area, then you need the variable area inside the function only and there is no need for the area outside the function.
let height = 20;
let width = 10;
function calculateArea() {
let area = height * width;
console.log(area); // area = 200
}
calculateArea();
This function will print the area, as expected.
But, what if we want to print the area again, we can just directly do console.log(area)
because we already have the area of the rectangle inside the area
variable, right? Let's see.
let height = 20;
let width = 10;
function calculateArea() {
let area = height * width;
console.log(area); // area = 200
}
calculateArea();
console.log(area); // ReferenceError: area is not defined
But this gave an error, did we do something wrong? No !!!, this is how the scope in JavaScript works. This accessibility of elements in JavaScript is known as Scope.
Scope in JavaScript is determined by the function or the block of code wrapped inside { }
.
For the above example, the scope of the area
variable was only inside the function calculateArea()
, outside the function area
is inaccessible.
The scope is like till where an element is relevant, variable area
is relevant only in the function calculateArea()
, thus its scope is inside function calculateArea()
only and not outside it.
Because the scope is limited to the function only, we can define the same variable(area
) inside multiple functions and it will not cause an issue. Let's try that.
Even though area
was defined at two different places, still it did not give any error because the scope of the first area
was only inside the function calculateArea()
and the second area
only had scope inside the function calculateAreaSquare()
.
Now, let's take a look at Nested Scope.
Nested Scope
When we have one scope inside another scope it creates a nested scope.
function outerFunction() {
let outerVariable = "I am outer function";
function innerFunction() {
let innerVariable = "I am Inner Variable";
console.log(outerVariable + " and " + innerVariable);
}
innerFunction();
}
outerFunction(); // "I am outer function and I am Inner Variable"
Inner Scope can access the variables of the Outer Scope but the same is not possible vice-versa. It is similar to how a living being inherits properties from their parents. A child inherits from parents but parents do not inherit from the child.
Similar to that, in the above example we can see that outerVariable
is inside function outerFunction()
(parent function) but still outerVariable
is accessible inside function innerFunction()
(child function)
Thus we can conclude two things,
- Scopes can be nested
- Elements of the outer scope are accessible inside the inner scope but not vice-versa
Now, as we have understood the scope and nested scope, let's start with Lexical Scoping.
Lexical Scope:
In simple terms, Lexical scope is the place where the item got created.
Example.
let myName = "Dan";
function call() {
console.log(myName);
}
call();
In the above example variable myName
is called inside function call()
i.e. local scope, but as we know Lexical Scope is the place where the item got created, not called, and in this example, the variable was initialized in Global Scope, thus the Lexical scope of variable myName
is Global Scope.
Let's look at another example:
function call() {
let myName = "Dan";
console.log(myName);
}
call();
Here Lexical Scope of myName
is call()
functional's local scope because myName
is created inside the function call()
.
Thus we can say that Lexical Scope determines the access to the elements in different scopes.
Scope Chaining
So, as we have seen Lexical Scoping decides the accessibility of variables, and that process is known as scope chain.
Scope Chain means nothing but looking for the required variable first in the local scope and if JavaScript engine does not find it there then it looks for it in the outer scope. Like how qualities are inherited from grandparents to parents and from parents to children. Similarly, a function can access elements of its parent scope a global or local/functional scope and that parent function can access the elements of its parent scope global or functional/local scope.
Let's understand this by an example.
// global scope
let globalVariable = "Global";
// outer function scope
function parent() {
let parentVariable = "Parent variable";
// inner function scope
function child() {
let childVariable = "Child Variable";
console.log(`I am ${globalVariable}, I am ${parentVariable} and I am ${childVariable}`);
}
child();
}
parent();
Here when the child()
function runs, it looks for globalVariable
, and as it is not in the local scope Js Engine searches for it in the outer scope i.e. the parent()
function scope but globalVariable
is not present there also thus Js Engine goes to the globalScope where the variable is present.
We can see a chain is formed from child()
-> parent()
-> global
. This is how a scope chain is formed.
Call Stack OR Execution Context Stack.
As we know Global Context gets executed first and when the Js engine finds any function it performs the Functional Context Execution.
First, the Global Context Execution is done then when the Js engine finds function function1()
it performs the Functional Context Execution. Then in the function1()
Js engine finds function2()
thus it executes FCE for function2 and as the execution of function2()
is completed it is removed from the stack, then after the execution for function1()
completes, it also gets removed from the execution stack and finally after the whole code is executed Global Context also gets removed from the stack.
This is how the call stack works in JavaScript.
Hoisting
Hoisting is the process where JavaScript enables us to use variables and functions even before they are initialized.
During scanning phase of execution context JavaScript engine scans for all the variable and functions and put it at the top of their scope.
When the variables are scanned during scanning phase, only declaration is done but those are not initialized.
// Variable lifecycle
let a; // Declaration
a = 100; // Assignment
console.log(a); // Usage
For variables with var
, default initialization is undefined
till the variable is actually initialized. For the variables with let
and const
default initialization is uninitialized
and thus when variables with let
and const
are used before initialization it gives Reference error.
Let's take a look at some examples.
Example 1
console.log(a); // undefined
var a = 10;
console.log(a); // 10
In the above example when the first line is executed, it does not give error because var a
is stored in global scope an it has its default declaration as undefined
. In the second line var a
is initialized and when third line is executed it returns 10.
Example 2
console.log(a); // Reference error
let a = 10;
Hoisting does not work same for let
and const
. For such variable if they are used before initialization it returns reference error because while scanning phase the variable declaration is not stored with initial declaration as undefined
.
The reason behind this reference error is TDZ-Temporal Dead Zone, we have discussed about this topic in another article.
The location at which the variables are stored before they are initialized is called TDZ Temporal Dead Zone. The
Function Hoisting.
Functions can also be hosted. Function hoisting allows us to call a function before it is defined.
greet();
function greet() {
console.log("Hello There");
}
Here we can see that, function is called before function declaration and it does not returns error. But this not same if function is stored in a variable
hello(); // TypeError
var hello = function greet() {
console.log("Hello There");
};
Hoisting can be beneficial in cases like all the functions can be called at the beginning and functions can be defined at the end which makes it easy for the person to focus on the execution rather than focusing on the other stuff.
Thanks for reading this article, hope you found it helpful. ๐