Behind the scenes with Javascript: An introduction.

Padmaja Bhol
8 min readAug 7, 2021

--

“JavaScript is a high-level, prototype-based object-oriented, multi-paradigm, interpreted or just-in-time compiled, dynamic, single-threaded, garbage-collected programming language with first-class functions and a non-blocking event loop concurrency model.”

Unreadable sentence right? That was basically a joke, but it's always a good practice to have a look at the bigger picture before diving deep into a topic. In this blog, I will try to explain how JavaScript works under the hood — something which is pretty important when it comes to writing better code or understanding other developer’s code. In the first section, I will try to deconstruct the previous monster definition.

Starting with High-level, high-level languages like JavaScript (and Python) doesn’t require a developer to manage resources manually but there’s also a downside, such languages will never be as fast and optimized as low-level languages like C. Another powerful tool in JavaScript that solves the memory problem for us is Garbage-collection. This automatically removes all unused objects from the computer memory (just not to clog it up with other stuff). As we know, the computer processor only understands 0s & 1s which is also called machine code. But is that practical? Since it's not, we end up writing human-readable code (mostly an abstraction over machine code). The process of converting this human-readable code into machine code is called compiling or interpreting. This process takes place inside the JavaScript engine. This special feature is what we refer to as a just-in-time compiled feature of JavaScript.

Another special thing about JavaScript is that it allows multi-paradigm. So what’s a paradigm? It's an approach and mindset of structuring code, which will direct the coding style and technique. Procedural programming, object-oriented programming(OOP), and functional programming (FP) are some of the most popular paradigms out there. Unlike most languages, JavaScript can do all of these and that is what makes it versatile. JavaScript has a prototype-based object-oriented approach. Almost everything in JavaScript is an object, except for primitive values like strings, numbers, etc. For example, arrays are just objects. Arrays are built from the prototype (which contains all the methods), and then inherit methods like push, etc from these prototypes.

JavaScript is a language with first-class functions, where functions are treated as just regular variables. Not only that, you can pass them into other functions, and return them from functions, this is what makes the language so powerful!

const closeModal = function () {  modal.classList.add("hidden");  overlay.classList.add("hidden");};
btnCloseModal.addEventListener("click", closeModal);

Here, we pass the function closeModal into another function as an argument and that is basically what first-class functions allow us to do!

We have defined Javascript as a dynamic language, here dynamic means dynamically typed. In JavaScript, we don't assign data types to variables, they are defined when the JavaScript engines executes the code. Along with that, the type of variables can easily be changed and reassigned and that is what dynamically-typed languages lets us do. But is it necessarily good? Many developers argue that JavaScript should be a strongly typed programming language as that will then prevent a lot of bugs. Well if we want to use JavaScript as a typed language, we can always explore Typescript.

At the end of the first section, let's now dig into single-threaded and non-blocking event loops. But before that let's first understand what a concurrency model is. Well, it's just how the JavaScript engine handles multiple tasks happening at the same time. But why do we need that? Well, it's because JavaScript runs in one single thread, so it can only do one thing at a time so we need a way to handle multiple things at a time. But what if there’s a long-running task? Sounds like it would block the single thread? We do want a non-blocking behavior here! This is where the event loop comes into play! It takes long-running tasks, executes them in the “background”, and puts them back in the main thread once they are finished. Everything that I covered in this section was just an overview and I will shed some more light on these topics in the upcoming sections.

In the second part, I'll put a bit more emphasis on the JavaScript engine and runtime. So what exactly is a JS Engine? It's a program that simply executes Javascript code. One of the most popular engines out there is the V8 engine. It not only powers Google Chrome but also Node.JS. So let's know more about the components of an engine and how it works! A JS Engine consists of a Call-stack and a Heap. The Call-stack is where our code is executed using something called the execution context. The Heap is where all our objects are stored.

Before we move to the topic of machine code, let's first understand the difference between compilation and interpretation. Compilation happens when the entire code is converted into machine code at once, and written to a binary file that can be executed by a computer. So the execution can happen way after compilation! But during interpretation, the interpreter runs through the source code and executes it line by line. In layman's words, the code is read and executed all at the same time. Earlier JavaScript used to be a fully-fledged interpreted language but there’s a downside to that, such languages are generally slower than compiled languages but do not worry! Modern JavaScript has solved that problem for us! Remember that low performance is no longer acceptable. Modern JavaScript uses a mixture of compilation and interpretation and this is what we previously referred to as the Just-In-Time compilation, in which the entire code is converted into machine code at once, and then executed immediately. The JS Engine consists of parsing, compiling, executing (in Call-stack), and most importantly, optimization that happens over and over again while the code is still running. All of these take place in special threads that we can’t access from code. And this is how just-in-time compilation works in JavaScript!

Now, let's understand the runtime in the browser. To make it simple, let's imagine the runtime as a container that includes all the things that we need to use JavaScript. The heart of any JavaScript runtime is the JS Engine and that is why it makes sense when we talk about both of them in a single section. In order for the runtime to work properly, we also need web APIs. Web APIs include DOM, timers, and console.log() method. These are the functionalities provided to the engine which are accessible on window objects but aren’t a part of the language itself. The runtime also includes Callback Queues, which is a data structure that includes all the call-back functions that are ready to be executed, i.e. event listeners like click, timer, data, etc. It's important to note that runtime can exist outside a browser too, for example, it works differently for Node.JS which does not provide web APIs. Instead, we have multiple C++ bindings and thread pools.

In the last section, we will learn about how the JavaScript code is executed. Though I earlier mentioned how this happened in the call stack, there’s actually even more to it! After compilation when the code moves into the execution part, a global execution context is created for top-level code. What exactly do I mean by top-level code here? It’s basically code that is not present inside of a function.

 const name = 'Padmaja'; const first = () => {
let a = 1;
const b = second(7,9);
a = a + b;
return a;
};
function second(){
var c = 2;
return c;
}
const x = first();

For example, the name variable is a top-level code and that results in its execution in the global execution context. Following that, we have two functions, first a declaration and second an expression. But the code inside these functions will only be executed when they are called.

But what exactly is an execution context? It's an environment in which piece of JavaScript is executed. It's like a box that stores all the necessary information for some code to be executed. This is then followed by function executions. For each function call, a new execution context is created and the same happens for methods. The engine then waits for callback functions to arrive in order to execute all of these. And remember who provides these callbacks? Yes, it's the event loop!

Now let's understand what exactly happens inside an execution context. Firstly, it contains a variable environment. Inside of this, all our variables and functions are stored. It also contains all arguments that were passed into the functions. Then we have the scope chain, which contains references to variables that are located outside of the current function. The context also gets a special variable called the this keyword. All of this content is generated during the “creation phase”, right before the execution. Here, it's important to note that arrow functions do not get their arguments nor do they get the this keyword.

Now lets go back to the code snippet that I earlier wrote to understand the creation phase. We will get a global execution context and one for each function.

Global :
name = 'Padmaja'
first = <function>
second = <function>
x = <unknown>
first() :
a = 1
b = <unknown>
second() :
c = 2
arguemnets = [7,9]

The values of x is marked as unknown since its declared in the first function so intitally we don’t know it yet. The b variable in first() is also set to unknown since it requires a function call. The second() function has an argument object since its an expression and not a declaration. All of this is put in the call stack, a place where execution contexts get stacked on top of each other, to keep track of where we are in the execution. The call stack is like a map for the JavaScript engine as it ensures the order of execution. The program now stays in this state forever until it is really finished and that happens when you close the browser. And that is how call stack works.

I have tried to give explain as much as possible about how Javascript works behind the scenes but there’s even more to it and I hope to write more about it in my upcoming blogs.

--

--

Padmaja Bhol
Padmaja Bhol

Written by Padmaja Bhol

Frontend developer, engineering undergrad, indic history enthusiast. NIT-R’24.

No responses yet