How JavaScript Works? – Behind the Scenes

JavaScript is a highly well-liked programming language that continues to grow in power over time. It is a simple, object-oriented, platform-independent language made specifically for managing browser data. It is a language with a singular scope that provides several libraries for various tasks.

JavaScript applications are referred to as "WORA" (Write Once, Run Anywhere) applications since this programming language enables the execution of additional code on any platform or browser without altering the original script.

Due to its growing popularity, numerous businesses are utilizing it at all points in their technology stack, including the front-end, back-end, full-stack, and embedded apps.

Our article will help you understand how JavaScript works from the inside out before delving deeply into JavaScript programming. We'll explore and understand how JavaScript works internally in this blog post.

We will cover the following:

  1. What is JavaScript?
  2. Components of JavaScript
  3. How does JavaScript Engine Work?
  4. Execution Contexts and Execution Stack
  5. How does the JavaScript Engine Execute Your Code?
  6. How does the JavaScript Engine Work in a Browser?

#1 What is JavaScript?

JavaScript is a high-level and single-threaded programming language. When we refer to a programming language as high-level, we mean that it has a high level of abstraction. As a result, it improves human understanding and usage.

Take a look at the sample below. If you had never used JavaScript, you would be able to tell right away that this line of code prints "Hello World!."

console.log('Hello World!');

As we said, JavaScript is a single-threaded programming language. It means that only one command is performed at a time.

JavaScript has a blocking and synchronous nature. To put it another way, the code runs in the order it appears, and each section of code must complete running before moving on to the next. Therefore, it is still possible to execute applications asynchronously and without blocking.

Type checking happens in real-time in a dynamically typed language like JavaScript. Therefore, when coding, programmers don't have to worry about the type of their variables.

var myRandomVariable = 1904;
let anotherRandomVariable = "Hey there!";
const one = 1;

They must only declare their variables with a simple var, let, or const.

#2 Components of JavaScript

We'll talk about the following JavaScript components:

  1. JavaScript Engine
  2. JavaScript Runtime Environment
  3. JavaScript Call Stack
  4. JavaScript Concurrency and Event Loop Mechanism

1) JavaScript Engine

JavaScript is an interpreted programming language, as you may already know. An interpreted language does not require compilation into binary code before execution.

So the question is, how do computers run those text scripts?

The JavaScript engine is responsible for executing JavaScript code. The JavaScript engine can be viewed as a device that runs JavaScript code, allowing the computer running the code to understand it without the need for interpreting.

One benefit of this method is that it makes the JavaScript platform independent, enabling it to run on any platform or operating system. All current Internet browsers include the JavaScript engine by default.

Now let's see how the JavaScript engine works.

The JavaScript engine runs each line of code in a JavaScript file sequentially as soon as it is loaded into the browser. Each line of code is analyzed from top to bottom, turned into binary code, and then run on the browser.

Every browser has its own JavaScript engine, however, the Chrome V8 engine is one of the most well-known JavaScript engines. The list of browsers and associated JavaScript engines is as follows:

  • Google Chrome: V8
  • Safari: JavaScriptCore
  • Mozilla Firefox: Spider Monkey
  • Microsoft Edge: Chakra

Two components make up the JavaScript engine:

  1. Memory heap
  2. Call stack

There is heap memory and a call stack in a JavaScript engine. Our code runs on a call stack. Heap memory is an unstructured memory pool that houses the objects our program needs.

2) JavaScript Runtime Environment

The JavaScript engine was covered in the section prior, although it does not operate independently. It functions inside the JavaScript Runtime Engine (JRE) along with other elements.

Web developers can make HTTP queries asynchronously due to the JRE, which also makes JavaScript asynchronous. JRE is made up of various parts, including:

  • JavaScript engine
  • Web API
  • Callback queue
  • Event loop
  • Event table

3) JavaScript Call Stack

JavaScript can only do one operation at a time since it is a single-threaded programming language with a single call stack. The call stack is a program that keeps track of our position inside it.

If we step into a function, that function will be pushed to the top of the stack and if we return from a function, that function will be popped off from the top of the stack.

Let's examine a JavaScript call stack example:

function addition(x,y) {
  return x + y;
}

function findSum(x,y) {
  var result = addition(x,y);
  console.log(result);
  return result;
}

findSum(4,5);

The JavaScript engine starts executing the code when you run the above program. The call stack will start empty, and from there, the process will proceed as shown in the following diagram:

A "Stack Frame" is the term used to describe each value displayed in the call stack.

JavaScript only uses one thread to execute code. Because you do not have to deal with some of the difficult situations you would have to in a multi-threaded environment, such as deadlock, thread safety, thread pool, etc., running code on a single thread can be relatively simple.

4) JavaScript Concurrency Model and Event Loop

If there are too many functions to run in the call stack for a single-threaded application, the browser becomes stuck and is unable to do anything. It is unable to render anything or run any additional code in the meantime.

That might be a significant issue from the perspective of the end user. Nobody wants their users to be stranded on a website while background processing is taking place.

While there are other issues with a single-threaded system, the unresponsiveness of the browser poses a more serious issue. Your browser will become temporarily unresponsive once it has finished processing a large number of tasks in the call stack. In this situation, the browser often asks the user if he wishes to close the webpage.

#3 How Does JavaScript Engine Work?

An overview of a JS engine's general operation is provided below:

Environment

JavaScript code cannot actually be "understood" by a computer, compiler, or browser. And if so, how does the code operate?

Behind the scenes, JavaScript always operates in a certain environment, the most popular of which are:

  • Browser
  • Node.js

Engine

When writing code in JavaScript, you use a human-readable syntax that includes letters and numbers. As previously stated, a machine cannot understand this kind of coding.

For this reason, each environment includes an engine.

In general, the engine's role is to take that code and translate it into machine code so that the computer processor can finally execute it.

The most popular engines for each environment are Chrome V8 (which Node also uses), Firefox SpiderMonkey, JavaScriptCore by Safari, and Chakra by IE.

Although they all operate similarly, engines differ from one another.

Additionally, it's critical to remember that an engine is nothing more than a piece of software; Chrome V8 is an example of a piece of C++ software.

Parser

We, therefore, have an environment, and within that environment is an engine. The engine uses the parser to check your code before starting to execute it.

The parser's task is to read through the code line by line and determine whether the syntax is proper because it is familiar with JavaScript syntax and rules.

The parser will halt operations and emit an error if it encounters a problem. The parser creates what is known as an Abstract Syntax Tree (AST) if the code is legitimate.

Abstract Syntax Tree (AST)

As a result, our environment has an engine, a parser, and an AST generator. However, what exactly is an AST and why do we need one?

Many other languages, including Java, C#, Ruby, and Python, also use the data structure known as the AST.

Interpreter

The interpreter must take the produced AST and convert it into an intermediate representation (IR) of the code.

Later on, we will study more about the interpreter because more background information is needed to completely comprehend what it is.

Intermediate Representation (IR)

What exactly is the IR that the interpreter creates from the AST?

An IR is a code or data structure that symbolizes the source code. Its function is to act as a bridge between machine code and code written in an abstract language, such as JavaScript.

Essentially, you can consider IR to be a machine code abstraction.

There are several types of IR; Bytecode is one JavaScript engine that uses frequently.

But perhaps you're asking... Why do we require an IR? Why not just convert the code directly to machine code?

Engines employ IR as a step between high-level code and machine code for two main reasons:

  • Mobility
    Code that is converted to machine code must be compatible with the platform it runs on.
  • Optimizations
    Both from code optimization and hardware optimization perspective, it is simpler to conduct optimizations with IR than with machine code.

Compiler

In our case, the interpreter developed Bytecode as IR, and it is the compiler's responsibility to convert Bytecode into machine code with certain optimizations.

Let's discuss some fundamental ideas and code compilation. We'll simply touch on it briefly for our use case because this is a vast topic that takes a lot of effort to grasp.

Interpreters vs. Compilers

Using a compiler or an interpreter are the two approaches to converting code into something a machine can execute.

An interpreter translates your code and runs it line-by-line while a compiler instantaneously converts all code into machine code before running it. This is the main difference between interpreters and compilers.

An interpreter is slower but simpler, while a compiler is faster but more sophisticated and slower to start.

To run high-level code, there are three techniques to convert it to machine code:

  1. Interpretation
    With this approach, you have an interpreter that reads and executes the code line by line (not so efficient).
  2. Ahead of Time (AOT) Compilation
    Here, the entire program is first compiled by a compiler before being run.
  3. Just-In-Time Compilation
    A JIT compilation strategy combines the best aspects of the AOT and interpretation strategies. It does dynamic compilation while also allowing for some optimizations, which significantly speeds up the compilation process.

Not all JavaScript engines, but the majority of them, use a JIT compiler. For instance, Hermes, the engine that powers React Native, does not employ a JIT compiler.

To sum up, the compiler in JavaScript Engines uses the IR produced by the interpreter to produce efficient machine code.

#4 Execution Contexts and Execution Stack

We provided a basic overview of the topic in the section above, and now we'll talk more about the sequence in which the code is executed.

The environment in which Javascript is run is known as the execution context. Consider execution contexts as a box containing our code and variables.

Global Execution Context (GEC)

This context is the default. Code that is not contained within a function GEC can only have one instance. If the code is written in strict mode, this value is unknown; however, if it is written in a global context, it returns a window object.

GEC contains every piece of code that isn't inside of a function. Therefore, any variables or functions that are outside of a function are in the GEC.

Everything you specify in a global context will be associated with the window object, which in the case of a browser will be a GEC object.

Execution Stack

If the code is outside of a function, it runs in the global execution context (GEC), but if it is inside of a function, it receives a brand-new execution context.

The new execution context is pushed on top of the global execution context in the stack data structure known as the execution stack. A new execution context will be created and put on top of the prior execution context if a new function is called inside the new execution context, which is essentially a function.

When do the items in the Execution Stack pop now that everything has been loaded onto it? It appears anytime the top-level execution context, which is essentially a function, completes its operation.

#5 How Does The JavaScript Engine Execute Your Code?

There is one fundamental idea that every JavaScript developer needs to be aware of if we want to grasp how JavaScript functions, and that is Execution Context.

An abstraction of the environment in which a specific piece of code is being performed is known as an execution context in JavaScript.

Both a global execution context and a functional execution context can be created by the JavaScript engine.

The global execution context is first established when the JavaScript engine begins reading the code. The creation phase and the execution phase are the two stages in which this occurs.

Four primary events occur during the global execution context formation phase:

  1. First, a global object is created.
  2. The global object is created and connected to another object called "this."
  3. Memory is allocated as a variable environment space for variables and functions.
  4. Variables declared using the "var" keyword receive the value "undefined," and function declarations are likewise preserved in memory.

In JavaScript, the third and fourth phases are frequently referred to as "hoisting."

#6 How Does The JavaScript Engine Work in a Browser?

In the previously mentioned example, we ran the application using the "Chrome" browser. A built-in "V8" JavaScript engine in the Chrome browser transforms and runs the new code line by line rather than doing so for the entire application.

A browser's JavaScript engine accepts the code and runs it to produce the result when a JavaScript program is run in that browser. A typical JavaScript engine's source code also passes through several stages before executing a program.

The figure that follows shows these stages:

Let's now examine each of these procedures in more detail.

Step 1: Parsing JavaScript file

When a JavaScript program is executed, the "Parser" component found inside the JavaScript engine is the first to receive the code. It fulfills its responsibility by line-by-line analyzing the code to determine whether or not it is syntactically correct. If an error is found, the parser throws an error and stops the execution of the code.

Step 2: Creating Abstract Syntax Tree

After reviewing the JavaScript code and confirming that there are no errors, the parser moves on to creating the "Abstract Syntax Tree" (AST).

Step 3: JavaScript Code to Machine Code Conversion

After parsing and generating the AST, the JavaScript engine turns the supplied code into machine code.

Step 4: Executing Machine Code

Finally, the system is asked to execute the converted code, and the appropriate byte code is then executed.

Summary

In today's technology world, JavaScript engines are present in the majority of current web browsers. The JavaScript engine Parser gets the code and validates it when a JavaScript program is executed. The provided code is subsequently translated into machine code and submitted for execution after creating an Abstract Syntax Tree.

You now understand how JavaScript works in the background. You gained knowledge of the phases and specifics of the Javascript Engine's operation as well as how the Execution context is built.

We hope this article gave you a complete view of how JavaScript works.


Monitor Your JavaScript Applications with Real User Monitoring

Atatus keeps track of your JavaScript application to give you a complete picture of your clients' end-user experience. You can determine the source of delayed response times, database queries, and other issues by identifying backend performance bottlenecks for each API request.

JavaScript performance monitoring made bug fixing easier, every JavaScript error is captured with a full stack trace and the specific line of source code marked. To assist you in resolving the JavaScript error, look at the user activities, console logs, and all JavaScript requests that occurred at the moment. Error and exception alerts can be sent by email, Slack, PagerDuty, or webhooks.

Try Atatus’s entire features free for 14 days.

Vaishnavi

Vaishnavi

CMO at Atatus.
Chennai

Monitor your entire software stack

Gain end-to-end visibility of every business transaction and see how each layer of your software stack affects your customer experience.