How JavaScript Works?

If you are among the ones who are curious to know how the JavaScript code works or you are planning to learn JavaScript then this article will amaze you and you will love the journey of learning JavaScript.

Understanding how the languages work is very important in order to build the apps that utilize the language to their best and develop the optimized software. In this article, we will be understanding one of the most popular languages of this era, JavaScript.

What is JavaScript?

JavaScript is a scripting or multiparadigm programming language that allows us to implement complex features on WebApps including frontend and backend. But is that enough about JS? The answer is No.
JavaScript is not only limited to the web but it can be executed everywhere including Drones, IoT devices, and many more. JavaScript only requires a JavaScript Engine to execute the JS program. We will understand the JS engine later. (Follow on Twitter @ TheNameIsAnkush to read the thread on JS engine when I post)

How does JavaScript work?

JavaScript is a synchronous single-threaded language. This means it can run only one instruction at a time and it will go to the next instruction only when the current instruction is executed completely even if the CPU has multiple cores and multiple threads available. This is because the JS engine has a single call stack that is responsible for executing the JS code.

Many developers say that “Everything in JavaScript is an object” but it is not right. JavaScript has primitives like string, boolean, number, null, undefined and symbol which are not objects.

JavaScript can be an interpreted language as well as a compiled language, it all depends on the JS engine implementation. But most(if not all?) of the modern JS engines use JIT compilation. Now, what is a JIT compilation?

JIT Compilation:

JIT is the Just In Time compilation. It is the best compilation than interpreter or compiler and it uses both compiler and interpreter. JIT compilation makes the JavaScript code work faster. (Explaining JIT compilation is out of the scope of this article. Follow on Twitter @ TheNameIsAnkush to read the thread when I post).

As JavaScript is a single-threaded language, it is easy to break the execution with the help of code that takes infinite time. To overcome this problem, we use the Event Loop. Event Loop is not a JS feature but this feature is provided by modern JS engines and its implementation may differ in various JS engines.

How JavaScript code is executed?

For executing the JavaScript code, we required JavaScript Runtime Environment(JSRE). JavaScript runtime environment consists of JS Engine, Web APIs, Event Loop, Callback queue(Task queue), Microtask queue, etc. Every web browser has a JavaScript Runtime Environment, hence web browsers can execute the JavaScript code.

When we run the JavaScript code, the first thing that happens is the creation of Execution Context. Execution Context is consists of two components, the memory component, and the code component. This execution context is created in two phases:

In this phase, JavaScript will go through the whole program line-by-line and it allocates the memory to all the variables and functions. As soon as it encounters a variable, it allocates the memory to that variable and assigns the value as undefined. The undefined is a primitive in JavaScript. And in the case of a function, the whole function code is stored in the memory space of the function name as a variable. The memory component will always have data in the key-value pair.

In this phase, JavaScript once again runs through the whole program line-by-line. Here the evaluation and function execution is done. In this phase, the variable in the memory component will get the actual value. When it encounters the function definition, it does not do anything and goes to the next line.
When a function invocation is encountered, a brand new execution context is created for that function. This new execution context is created in the code component of the Global Execution Context. This new execution context will also be created in two phases, as discussed earlier. In this new execution context, the code inside the function body will be taken into consideration.
When a return keyword is encountered in the function execution, the return keyword tells the function to just return the whole control back to the execution context where the function was invoked. Here, the whole execution context created by the invoked function will be deleted completely.

For managing the creation, deletion, and control management of execution context, JavaScript manages a stack known as Call Stack.
At the very bottom of the call stack, we have a global execution context. When a function is invoked, it will create a brand new execution context. This new execution context is pushed into the call stack and execution control goes to this new execution context.
At the very end of the program, the global execution context is also removed and the call stack will become empty.

To make you understand this clearly, let me explain it with the help of examples and visual representation.

The following code has one variable a and a function b(). The function b is invoked at line no. 7.

var a = "ankush";function b() {
console.log("hello");
}
b()

As we have discussed earlier when we run the code, the first thing that happens is the creation of execution context. To test this, let's put a breakpoint at line no. 1 and run the code. Here, the code execution will be stopped at line no. 1, and at this point, no code is executed.

If we use the developer tools provided by the web browser, we can see in the call stack that the global execution context is pushed in the call stack. The global execution context is named anonymous here.

Global Execution context pushed to the call stack

If we look at the scope, we can see that the variable with the name a is created on a global scope and the value of this variable is undefined. And the variable b is created that has the code of function b. We can check the same from the console as well.

Check memory component of execution context through the scope.

If you could see, we haven’t executed the code yet, but JavaScript knows that we have one variable named a and one function named b. This is the reason, why we can use the variables and functions before defining them. (More explanation on this is out of the scope of this article. I will cover this in another article or I will post a thread on Twitter here).
Now if we step into the code and execute line no. 1, we can see that the variable a in the global scope will now have the value “ankush”.

Actual value assigned to the variable inside memory component of execution context

After executing line no. 1, the next code is the function from line no. 3 to 5, this code will be skipped because it is not invoked yet. The control will now go to line no. 7 where the function b is invoked. When line no. 7 is executed, the brand new execution context will be created for function b inside the code component of the execution context from where function b is invoked.

Let me put a breakpoint at line no. 4 so that we can pause function b execution and see the new execution context.

New Execution Context is created on the function invocation

We can see in the above image that the new execution context named b is pushed to the call stack and the blue arrow shows that the current control is at line no. 4 of the execution context b. This new execution context will have its own memory component and code component and the code inside function b will be executed in this execution context b. The variables and functions inside function b will be present in the Local scope. In the image below, we can see that the local scope is created for function b.

Checking the memory component of the execution context of the invoked function

The scope of function b has its local memory and the lexical environment of its parents. (I will post more about this on the Twitter thread here).

On executing line no. 4, the hello will be logged to the console.

The output is logged to the console

The function b has no return statement. When line no. 5 is executed, the execution of function b will be completed. At this point, the execution context of function b will be removed from the call stack and execution context b will be deleted completely.

The execution context of invoked function is removed from the call stack when the function execution is completed

When the last line of this JavaScript program is executed, the global execution context will also be removed from the call stack and deleted completely. At this point, the call stack will be empty.

The global execution context is removed when the JS program is executed and the call stack is empty

This is the flow that JS programs follow when they execute. If you understand the execution context clearly, you will understand the execution of the JS program easily.

There are some more concepts like Scopes and Block Scope in JavaScript, Hoisting, Higher-Order functions, Shadowing, Closures, Event Loop, JS Engine, etc which are out of the scope of this article. If you want to know more about these topics with some tips, do follow me on Twitter, I’m will be posting threads for these topics.

I hope you have learned the working of the JS program and gained some knowledge. Thanks for reading this article.

For any help or suggestions connect with me on Twitter at @TheNameIsAnkush or find me on LinkedIn.

Tech blogger, researcher and integrator