Every browser has its own JS engine, but the most well known one is Google's v8 Engine. This v8 Engine powers Google Chrome and also Node.js (used to build server side applications).
In this article, you'll learn more about what a JS engine is and how it works under the hood.
Here's what we'll cover in this quick guide:
Any JS engine always contains a call stack and a heap.
The call stack is where our code gets executed with the help of the execution context. And the heap is an unstructured memory pool that stores all the objects in the memory that our application needs.
You should now understand where the code gets executed – but now the question is how the code gets compiled to machine code so that it can execute afterwards. But before this, let's understand in brief about compilation and interpretation.
Compilation vs Interpretation
In compilation, the entire code is converted into machine code at once and written in a binary file that can be executed by a computer.
In interpretation, the interpreter runs through the source code and executes it line by line. The code still needs to get converted into machine code, but this time it is happening line by line while executing the program.
JS used to be a purely interpreted language. But the modern JS engine now use a mix of compilation and interpretation which is known as "just-in-time" compilation
In JIT compilation, the entire code is converted into machine code at once and then executed immediately.
You may wonder what's the difference between the compilation and JIT. Well, there's one major difference: after compilation, the machine code is stored in a portable file. It can be executed at any time – there's no need to rush immediately after the compilation process.
But in the case of JIT, the machine code needs to execute as soon as the compilation ends.
I'm not going to go in-depth into these concepts, but now you hopefully understand the basics of the compilation, interpretation and JIT.
During this parsing process, the code is parsed into a data structure called the AST (Abstract Syntax Tree). This works by first splitting up each line of code into pieces that are meaningful to the language (like the const or function keywords), and then saving all these pieces into the tree in a structured way.
This step also checks if there are any syntax errors. The resulting tree will later be used to generate the machine code.
For example, let's take a look at an AST for this line of code
const greet = "Hello":
You don't need to know or understand how ASTs work, this is just for curiosity.
You can play around and generate your own AST here.
The next step is compilation. Here the engine takes the AST and compiles it to machine code.
Then, this machine code gets executed immediately because its using JIT – remember that this execution happens in the call stack.
But this is not the end. The modern JS engine generates inefficient machine code just to execute the program as fast as possible. Then the engine takes the already pre-compiled code to optimise and recompile the code during the already running program execution. All this optimisation happens in the background
The core of a JS runtime is the JS engine.
But the engine alone isn't enough. In order to work properly, we need Web APIs.
For example, we attach event handler functions to DOM events like a button to react to certain events. These event handler functions are also called callback functions. So, as the click event happens, the callback function will be called
Here is how it works behind the scenes:
- First, after the event occurs, the callback function is put into the callback queue.
- Then, when the call stack is empty, the callback queue gets passed to the call stack so that it can be executed and this happens by something called the Event Loop.
So, in short, the event loop takes callback functions from the callback queue and puts it into the call stack, so that it can be executed.
That's it! You now know the basics of how the JS Engine and JS Runtime work.