How the browsers understand JavaScript

Mustafa Abdelmogoud
5 min readSep 23, 2019

--

Photo by Markus Spiske on Unsplash

Here is the second part of How the browser renders your website, In the first part, we focused on the HTML & CSS in this part we are going to focus on JavaScript.

Here is the first part if you missed it.

Once you visit a website using any browser. your browser makes an HTTP GET request to load the HTML page including a <script> tag. the browser makes a separate request to load this script.

Your browser receives a minified JS code from the server. the main job for the JavaScript engine starts here.

The JavaScript Engine is responsible to handle the JavaScript code, different browsers have different JavaScript engines.

Throughout this article, I will focus on the V8 engine. V8 is used in the Google Chrome browser and it’s also the main engine for Nodejs.

This is an overview of the steps that we are going through whenever we execute a piece of JavaScript code.

Let’s go step by step, to understand what is happening under the hood.

Tokenization

In this first step, the engine converts the string of code into an array of tokens. the engine walks through the code character by character and word by word to classify each of them.

For example, if you have a line of code like this let x = 10; the engine will convert it into an array of tokens like this

[K(LET, “let”, 0), T(WHITESPACE, “ “, 0),
T(IDENTIFIER, “x”, 0), T(WHITESPACE,” “, 0),
T(ASSIGN, “=”, 2), T(WHITESPACE, “ “, 0),
T(SMI, “10”, 0), T(SEMICOLON, “;”, 0)]

Note I will not explain this array of tokens here, but if you studied compiler theory maybe you find it familiar to you.

Then the engine starts the second step to generate the “the Abstract Syntax Tree (AST)” using this array of tokens.

Parsing

The V8 engine starts to convert this array of tokens into an Abstract Syntax Tree, this data structure represents our code. One of the most important things in the parsing step is that the engine determines the scope of each variable.

For example, if we have a function like this

function add(x, y) {
return x + y;
}

The engine will convert it into an AST like this:

— — AST — -
FUNC at 12
. KIND 0
. SUSPEND COUNT 0
. NAME “add”
. PARAMS
. . VAR (0x7fbd5e818210) (mode = VAR) “x”
. . VAR (0x7fbd5e818240) (mode = VAR) “y”
. RETURN at 23
. . ADD at 32
. . . VAR PROXY parameter[0] (0x7fbd5e818210) (mode = VAR) “x”
. . . VAR PROXY parameter[1] (0x7fbd5e818240) (mode = VAR) “y”

You can see the AST for any code sample using the V8 engine using this command:

$ d8 -- print-ast <file-name>

And here is visualization for this AST:

Then the engine starts to generate the Byte-code which is executable code.

Interpreter / baseline compiler

Here is where the most confusing part starts, JavaScript is a compiled language but it’s different from what we know about classic compiled language like C. C language is statically typed language using AOT compilation, but JavaScript is dynamically typed language using JIT compilation, and here is a fast comparison between this two

AOT

Ahead Of Time compilation, In this type, the compilation is a separate process and it happens only once, the result of this process is an executable file you that can run it.

JIT

Just In Time compilation, it’s not a separate process, it happens during the runtime. In this step the engine compiles line by line and collects info(this is called “profiling”) about the code to use it later in the recompilation step.

V8 has a component responsible for this part called Ignition, if you checked the V8 source code you will find the BytecodeGenerator which is part of the Ignition component that generates the Byte-code. during this process the JavaScript engine does a process called profiling to collect some information about the code to use it later in the optimization step.

You can see the Byte code for any code sample using this command:

$ d8 -- print-bytecode <file-name>

At this step the JavaScript code is running. The next steps will be just for optimization.

Profiling

Profiling is part of the interpreting step, engine collets information about the code like Data Types and marks the hot function then store this info in Feedback Vector.

Hot function means this function is used many times, then the engine marks this function as hot.

you can see these data using this command

$ d8 — prof <file-name>

for more info about profiling watch this talk

https://www.youtube.com/watch?v=u7zRSm8jzvA​

Optimization Compiler

TurboFan is the optimization compiler inside V8, based on the info that Ignition collected it,TurboFan starts to optimize the hot functions for better performance. TurboFan depends on inline caching to save this optimized function to use it instead of the original function.

photo by Franziska Hinkelmann

whenever is this inline caching is invalid the engine back to execute the Byte code instead of this optimized code, this called deoptimization.

Avoid Deoptimization

there is a lot of tips and tricks to avoid this deoptimization step to have better performance for your website like

  1. try to make your code look like statically typed languages.
  2. avoid holly arrays.

--

--