Introduction
In the early stages of your web development journey, it's okay to learn HTML, CSS, some JavaScript methods, and a library like React to create awesome, cool-looking websites. Personally, I provided a billing app to one of my clients just by learning the basics of the above technologies and some skills in creating APIs using ExpressJS. It's alright to not fully understand the details of what's happening under the hood as an entry-level web developer or someone like me (I'm primarily a Mechanical Engineer). However, as you encounter errors and find your debugging skills insufficient to solve them, you'll feel the need to deep dive into this language. To become a proficient developer, mastering these skills becomes inevitable. You can't escape it. On the other hand, interviewers have the good (!) habit of asking those head-spinning questions to gauge your knowledge of JavaScript. Believe me, this series will help you a lot.
High level view
In this series, we will focus on the Google Chrome browser, starting with the loading of the source code. 'Blink' is Google Chrome’s rendering engine, responsible for various tasks, including fetching the source code from the network and rendering web content by interpreting HTML, CSS, and other resources to construct the DOM and CSSOM. When Blink needs to execute JavaScript code, it integrates the V8 engine. Blink warrants a separate series for a more in-depth discussion, which we will leave for future sessions.
From a high-level perspective, we require a JavaScript runtime environment to execute our JavaScript code. This runtime environment could be a browser, a server, a smartwatch, or even a washing machine if it operates using a JavaScript program.
In a JavaScript runtime environment, we need the below elements.
- JavaScript Engine
- Web API
- Event loop
- Task queue
- Microtask queue
The Engine
The JS Engine is the heart of the JavaScript runtime environment. Every browser has a JS Engine.
- Chrome has V8. Not only the Chrome, V8 also powers Node.js and Deno.
- Firefox has SpiderMonkey
- Edge has Chakra
- Safari has JavaScriptCore
The most important protocol for a JS Engine is to follow the ECMAScript Standard, which is the governing body of JavaScript. The first JS Engine, SpiderMonkey, was created by Brendan Eich, the creator of the JavaScript language. The initial JS Engine was implemented in Netscape (now Mozilla Firefox).
Regardless of the specific engine, they all operate in a similar manner, typically being written in C/C++. When we execute our JavaScript code, it actually runs within the JS Engine. Let's take a bird's-eye view of the V8 engine.
Source code
As mentioned earlier, the source code is received from Blink, the render engine. The source code is actually the code we write as a developer. It has the variables, objects, functions, arrays etc.
Tokenizer
After taking the code, the engine should process it in ‘tokenizer’ because, the code intended to be executed by the CPU, which does not know JavaScript. During the tokenization phase, the code is broken down to ‘tokens’.
Tokens are blocks of one or more character that have a single semantic meaning: an operator like ‘=’, an identifier, a string. You may heard the terms lexical analysis, lexing. Lexical analysis is a process used to breaking the code into tokens by analyzing character after character of the code. In V8 engine, it is called by ‘Scanner’. It tries to find the the keywords in our code and tries to understand what we meant and constructs tokens by combining consecutive characters in an underlying character stream.
Parsing
The parser checks the code using tokens provided by the 'Scanner.' One of its tasks is to ensure that the syntax complies with the ECMAScript standard. If any errors are found, it halts and outputs the errors to the console.
Suppose, we have below code.
let x = 10
The parser will break down the code in each of the tokens we wrote.
There is another thing called ‘Syntax Parser’. It read the code tokens after tokens and see if the grammar is correct.
Abstract Syntax Tree
The code parser, upon confirming the code's correctness, proceeds to generate an Abstract Syntax Tree (AST) – a hierarchical structure of nodes representing the code's syntax. We can compare it with the DOM nodes. There is a website (AST Explorer) where you can play with your source code and AST.
var message = 'Hello';
For the above code, AST would be like below.
This is the AST for a simple one line of code. Think about your code !
Interpreter & Compiler
When the AST formation is finished it will be pushed to the interpreter and thrown in to a compiler or profiler. In the case of V8 engine, its not a compiler or interpreter. It is a JIT-compiler. Its called the ‘Ignition’. Its actually a hybrid of both interpreter and compiler. JIT helps to execute the code in runtime rather compile all the code before execution. Ignition produces Bytecode (platform independent code) which is an abstraction of machine code, able to be executed by compiling to non-optimized machine code. In V8, the bytecode is executed in main thread while ‘Turbofan’, the optimizing compiler, makes some optimizations in another thread and produces optimized machine code for your machine’s CPU.
Execution
JavaScript is a single-threaded language which has single sequential flow of control. It is a synchronous language with asynchronous capability. That mean, at the core, JS is synchronous. In one and only thread the execution of the code runs one after another with the help of Call Stack and memory.
Call Stack
JavaScript is a single threaded language with a single Call Stack. So, we can say that the code runs synchronously. The machine code received from Ignition and Turbofan and stored in the Call Stack and heap memory depending on what is going to store. Call Stack is the memory place which consists of stack frames. Every stack frame is associated with a call to a function and the hold there until the function is terminated.
A stack frame is composed of below three things
- local variables
- argument parameters
- return address
Some of the intermediate data is stored in Call Stack.
The call stack relates with the concept we call as ‘Execution Context’ which is created in every function call. We will discuss about it in upcoming episode.
Memory Heap
Some data like array, object, functions etc. are saved in heap.
Browser store data in two different places (Call stack and heap)
- Trade Space for speed - A call stack requires unlimited space in memory to make it faster to process. But, continuous memory is not possible due to hardware limitation. Browser designers set a max limit for the memory space. This is why we get stack-overflow error. Typically, browser save data in call stack in limited size. Integers and other primary data type variables can be stored here.
- Trade speed for space - Heap does not require continuous space to save extensive data like an object. Trade off is that the process with heap is slow.
Garbage Collection
Low-level language like C C++ require to allocate and free up memory manually. Which is hell of task for a developer to work with. Higher level languages like JavaScript, Python, C# etc. automatically allocates memory when the objects are created and free up memory when they are not required anymore. This process is called Garbage Collection (GC). GC is crucial for memory management and prevent memory leaks.
V8 primarily use an algorithm like Mark-and-Sweep. In Marking phase, the garbage collector moves through well-known roots like global object, local variables etc. It marks all objects and values that are reachable, means in use. While in Sweep phase, the garbage collector sweeps through the heap, deallocating memory for unreachable objects. Thus it removes the unreachable object as considered garbage. Some other concepts like Orinoco : Young Generation Garbage Collector are there which we will describe in later episode.
Browser API
Web APIs are the part of the JS runtime environment provided by the browser. It provides the functionality of the browser to the engine. It is not part of the JS language. With the help of web APIs, the engine can manipulate DOM, fetch data from external APIs, draw and manipulate graphics etc.
Callback/Task queue
Callback queue (also known as ‘message queue’) includes the callback functions and events from async operations which are ready to be executed. Tasks get enqueued to the task queue when event fires a callback or timeout/interval is reached. The callback functions are executed FIFO method and get passed into the call stack when it’s empty.
Microtask queue
It is similar like task queue but it has higher priority than task queue. Promise handler and mutation observers go through the microtask queue.
Event loop
Event loop works like a infinite while-loop. it constantly monitor the state of the call stack and the callback queue. If there is a callback function waiting to be executed, event loop waits for the call stack to be empty and while it gets empty the event loop pushes the callback function in the stack. Due to this nature JS can run asynchronously though it (JS) is a single threaded language.
Summary
This was a bird eye view of the JavaScript Runtime Environment inside the Chrome. I would hesitate to label myself as an expert in any way. Any criticism will be helpful. Hope you see in the next episode.
Top comments (0)