JavaScript : Magic behind the curtains

JavaScript : Magic behind the curtains

ยท

6 min read

JavaScript Engine

JavaScript Engine is a program that executes JavaScript code. They are typically developed by web browser vendors and every browser has one.

V8 from Google is the most used JavaScript engine. Google Chrome and many other Chromium-based browsers use it. SpiderMonkey is developed by Mozilla for Firefox. JavaScriptCore is Apple's engine for its Safari browser. Chakra is the engine of the Internet Explorer browser.

Stack and Heap

Every engine contains a stack (call stack) and heap memory.

i. Call stack :

In call stack, our code is executed using an execution context.

ii. Heap memory :

In heap memory, all the objects required by the application are stored.

Compilation VS Interpretation

As we know, computers only understand zeros and ones, therefore every single program ultimately needs to be converted to this machine code. This can happen using compilation or interpretation.

In compilation, the entire program is converted to machine code at once. Then, a portable file of this machine code is created which can be run on any computer.

In interpretation, there is an interpreter which runs the source code and executes it line by line. Here, the conversion of source code happens right before execution.

JavaScript used to be a purely interpreted language. Interpreted languages are slower than compiled languages and that was okay for JavaScript. However, now that JavaScript is used in web applications that we build today, needs fast execution. Hence, JavaScript uses Just-in-Time Compilation.

Just-In-Time Compilation

In this approach, the source code is converted into machine code at once and then executed right away. There are two steps involved but no portable file is created.

As a piece of JavaScript code enters the engine, the first step is to parse the code i.e read the code. During parsing, code is parsed into a data structure called Abstract Syntax Tree. It splits the code into pieces that are meaningful to the language and then saves it in the tree. This also checks the syntactical errors.

For compilation, the AST would generate the machine code. This machine code is executed right away.

JavaScript Runtime

We can think of JavaScript runtime as a big container which contains all the things we need to use JavaScript (Browser in this case). The most important component of the runtime is the JavaScript engine. However, the engine alone is not enough.

We need web APIs which are the functionalities provided to the engine on the window objects but are not part of JavaScript.

Another important thing is a call-back queue. It is a data structure that contains all the call-back functions, which are ready to be executed. When a call stack is empty, the call-back queue is passed to the stack to execute these functions. This happens with the help of an event loop.

Execution Context

An abstract concept of an environment in which a piece of JavaScript is executed. It stores all the necessary information for some code to be executed.

a. Variable environment

It stores variable declarations (let, const, var), functions and argument objects.

b. Scope chain

It consists of references to variables that are located outside of the current function.

c. 'this' keyword

It is a variable that points to the owner of the function.

All of these are generated during the creation phase, right before execution.

In this case, there are three execution contexts only. In real-world projects, there are innumerable execution contexts. How do we order the execution of functions then? That's when the call stack comes into the picture.

Call Stack

In call stack, execution context gets stacked on top of each other to keep track of where we are program's execution. The execution context on the yop is the currently running one. Once its execution is completed, it is removed from the call stack and the next execution context is executed until stack is empty.

Variable Environment

Let's see a few concepts related to variable environment.

Hoisting

The process of making some variables accessible in the code before they are declared is called hoisting.

Before execution, the code is scanned for variable declarations and for each new variable, a new property is created in the variable environment object.

Function declarations are hoisted but function expressions and arrow functions depend on the declaration type (var, let, const).

'var' declarations are hoisted but 'let' and 'const' declarations are not hoisted because these variables are placed in Temporal Dead Zone.

Temporal Dead Zone

Temporal Dead Zone is the area of a block where a variable is inaccessible until the moment the computer completely initializes it with a value.

Scope

A scope is a place or environment in which a variable is declared. That's the same as the variable environment in the case of functions. The scope of a variable is the region in which a particular variable is accessible. JavaScript follows lexical scoping - controlled by the placement of functions and blocks in the program.

a. Global scope

The variables declared outside any function or block are said to be in the global scope. They can be accessed from every part of the program.

const myName = 'Tanishka';

function greetings(myName) {
    const greet = `Hey, ${myName}`;
    return greet;    
}

const finalGreet = greet(myName);

// Here, variables 'myName' and 'finalGreet' have global scope.
// They can be accessed from every part of program

b. Function scope

The variables declared in a function are said to be in the function scope or local scope. They can be accessed only inside that function.

const myName = 'Tanishka';
function greetings(myName){
    const greet = `Hey, ${myName} !`;
    console.log(greet); // Hey Tanishka;
}
console.log(greet); // greet is not defined

c. Block scope

The variables declared in a particular block (like if block, for loop block, etc.) are said to be in the block scope. They can be accessed only inside that block. This applies only to 'let' and 'const'. Variables declared using 'var' can be accessed outside the block.

const bool = true;

if(bool === true){
    const greetConst = 'Hey !';
    console.log(greetConst); // Hey!
    var greetVar = 'Hello !';
    console.log(greetVar); // Hello !
}

console.log(greetVar); // Hello !
console.log(greetConst); // greet is not defined

Scope chaining

Every scope always has access to all the variables from its outer scope. This is called scope chaining.

JavaScript engine searches for the value of the variables in the scope of the functions. However, the search is in a lexical manner. First of all the engine looks in the current scope of the current function. If not found, it finds it in the parent function. This is called variable lookup.

Scope chaining is a one-way street - scope will only have access to variables from its outer scopes and never inner scopes.'

'this' keyword

It is a special type of variable that is created for every execution context. It points to the owner of the function in which 'this' keyword is used.

If 'this' keyword is used in a function in strict mode, it would return undefined but in strict mode, it would return the window object.

An arrow function does not have its own 'this' keyword. When 'this' keyword is used in an arrow function, it points to the parent of the arrow function.

In an event listener, 'this' keyword points to the window object.


Hope you learned something new from this blog! ๐Ÿ˜„

ย