High level view
- Web API
- Event loop
- Task queue
- Microtask queue
- Chrome has V8. Not only the Chrome, V8 also powers Node.js and Deno.
- Firefox has SpiderMonkey
- Edge has Chakra
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.
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.
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.
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.
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.
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.
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 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.
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 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.