How The JavaScript Engine And Runtime Work
JAVASCRIPT ENGINE AND RUNTIME
A JavaScript Engine is a computer program that executes JavaScript code. Every browser has its own JavaScript engine; the most popular one is Google’s V8 engine which powers Google Chrome and also powers Nodejs.
Of course, all other browsers have their own JavaScript engines.
Microsoft Edge browser’s JavaScript engine used to be Chakra before it was switched to the V8 engine.
Let us dive deep into it.
CALL STACK AND HEAP
JavaScript engines contain a call stack and a heap. The call stack is where your code is executed using what is known as “execution context”. The Heap is an unstructured memory pool that stores all the objects our application needs.
Now, you need to understand how the code is compiled into machine code so that it can be executed.
Before that, you need to understand two key concepts and their differences: Compilation and Interpretation. In a previously published article here, I wrote that computers only understand 0s and 1s. Therefore, every code you write has to be converted to this machine code that the computer understands. This happens via either Compilation or Interpretation.
COMPILATION AND INTERPRETATION
In Compilation, the entire source code gets converted to machine code at once. The machine code is then written into a binary file that can be executed by any computer. Summarily, the machine code gets built and then gets executed in the CPU. This execution can happen much later after the compilation. For example, any application running on your computer has been compiled before, you are executing it on your computer.
In Interpretation, there is an interpreter that runs through the source code and executes the code line by line. You do not have the two steps we saw in the Compilation procedure. Here, you have the code being read and executed all at the same time. Yes, the source code has to be converted to machine code, this happens right before it is executed and not ahead of time.
JavaScript used to be a solely interpreted language. The problem with such languages is that they are much slower than compiled languages. The slow speed used to be acceptable for JavaScript, but with modern JavaScript, it is no longer acceptable. Imagine using Google Maps on your phone and it takes two seconds to respond to every drag you make on it - that would be a horrible user experience.
A lot of people still think JavaScript is an interpreted language, but that is not really the case anymore.
Modern JavaScript uses a mixture of compilation and interpretation and this is called just-in-time compilation. This mixture compiles the machine code all at once and executes it right away. Essentially, we have the two steps of regular ahead-of-time compilation, however, there is no binary file to be executed. Execution happens immediately after compilation.
This is perfect for JavaScript and is a lot faster than executing code line by line.
JAVASCRIPT CODE EXECUTION MECHANISM
Let us go deeper in our dive. As a piece of JavaScript code enters the JavaScript engine, it gets parsed meaning it gets read. During this parsing phase, it gets read into a data structure called the Abstract Syntax Tree or AST. This works by splitting each line of code into meaningful pieces, like const or function keywords and then these pieces are saved into the tree in a structured way. This step also checks for syntax errors and the resulting tree will later be used to generate machine code.
Take note that the Abstract Syntax Tree does not correlate with the DOM tree. The next step is Compilation which takes the generated AST and compiles it into machine code. The machine code then gets executed right away because modern JavaScript uses just-in-time compilation.
Do not forget that execution happens inside the JavaScript engine in the call stack.
Your code is now running and you are done, right? No. How so? Well, the JavaScript engine first creates an unoptimized version of machine code in the beginning just so that it can start executing as fast as possible. In the background, this code is being optimized and recompiled during the already-running program execution. This can be done multiple times and after each optimization, the unoptimized code is simply swept aside for the optimized code without stoppage of execution.
This process is what makes modern engines such as V8 so fast.
So you have seen how your code gets executed in the background in the JavaScript engine.
JAVASCRIPT RUNTIME
We now proceed to look at what JavaScript runtime is, particularly the most common one, which is the browser. Doing this will make you understand the bigger picture of how JavaScript works altogether.
We can picture JavaScript runtime as a big box or container which includes all the things that we need in order for us to use JavaScript in the browser (runtime). The heart of any JavaScript runtime is the JavaScript engine.
However, the engine alone is not enough. For it to work properly, we need access to the web APIs (DOM, timers, fetch, console.log() etc.). Essentially, web APIs are functionalities provided to the engine which are not part of the JavaScript language itself. JavaScript simply gets access to these APIs through the global window object.
A typical JavaScript runtime contains what is known as a “Callback Queue”. This is a data structure that contains all the callback functions that are ready to be executed. For example, event handler functions are attached to DOM elements like a button to react to specified events. These event handler functions are called callback functions.
If for example, it is a click and it happens, the callback function will be first put into the callback queue. Then when the call stack is empty, the callback function gets passed to the stack so that it can be executed. This happens via the Event Loop. The event loop takes the callback functions from the callback queue and puts them in the call stack. I discussed briefly here how the event loop is how JavaScript's concurrency model is implemented and the previous paragraph in this article sums it up.