• JavaScript Visualized: The JavaScript Engine
  • Written by Harsh Patel
  • Translation from: The Gold Project
  • This article is permalink: github.com/xitu/gold-m…
  • Translator: No problem
  • Proofreader: KimYangOfCat Usualminds

Visual JavaScript engine

As JavaScript developers, we usually don’t have to deal with the compiler ourselves. But it’s also good to know the basics of the JavaScript engine and how it takes our different JS code and turns it into something the machine can understand! 🥳

Note: This article is primarily based on Node.js and the V8 engine used by Chromium-based browsers.


The HTML parser looks for the script tag in your code and its corresponding source, and loads the program or code from that source. It can come from the network, temporary storage, or another service worker. It then responds in the form of a byte stream that is later taken over by the byte stream decoder! The main byte stream decoder decodes incoming stream data.


The byte stream decoder creates a token based on the decoded byte stream. For example, 0066 is decoded to f, 0075 to U, 006E to n, 0063 to C, 0074 to T, 0069 to I, 006f to O, 006e to n ‘and a space. It looks like you wrote function! This is a reserved keyword in JavaScript, and a token is created and sent to the parser (as well as the pre-parser, which is not covered in the GIF but will be explained later). The same is true for the rest of the byte streams.


The engine uses two parsers: a pre-parser and a parser. To reduce the time it takes to load a site, the engine tries to avoid parsing code that is not immediately available. The preparser handles code that might be used later, and the parser handles code that needs to be used right away. If a function is only called when the user clicks a button, there is no need to compile it when the site loads. If the end user clicks the button and needs the code, it is sent to the parser.

The parser creates nodes based on the tokens it receives from the byte stream decoder and uses these nodes to create an abstract syntax tree (AST). 🌳


Next, the interpreter appears! The interpreter traverses the AST and generates bytecode based on the information contained in the AST. Once the bytecode is generated, the AST is removed to clear the memory space. Finally, we have something that the machine can use. 🎉


Although bytecode is fast, it can be even faster. The bytecode generates some information when it runs. It can detect whether certain behaviors occur frequently and what type of data is being used. Maybe you’ve called a function dozens of times, so it’s time to optimize and make it run faster. 🏃 🏽 ♀ ️

The bytecode, along with the generated type feedback, is sent to the optimizer compiler. The optimization compiler receives bytecode and type feedback and generates highly optimized machine code from it. 🚀


JavaScript is a dynamically typed language, which means that data types can change over time. If the JavaScript engine needs to check the data type of a value every time, it will run very slowly.

To reduce code interpretation time, the optimized machine code only deals with situations that the engine has seen before when running bytecode. If we reuse a piece of code that returns the same data type over and over again, we can simply reuse the optimized machine code to speed things up. However, JavaScript is dynamically typed, and the same code can suddenly return a different data type. If this happens, machine code performance degrades, and the engine falls back to interpreting the generated bytecode.

If a piece of code is called 100 times and returns the same value so far. So the engine is going to assume that when you call it on the 101st time, it’s still going to return that value.

Let’s say we have a summation function like the following, which so far takes a numeric argument every time it is called.

This function returns the number 3! When we call again, the engine assumes that we are still carrying two numeric parameters.

If this is true, then dynamic checking is not required and the engine can again use the optimized machine code. On the other hand, if this assumption is incorrect, the engine will fall back to using the original bytecode instead of optimized machine code.

Let’s say the next time we call it we pass a string instead of a number. Because JavaScript is a dynamically typed language, we can do this without raising an error.

This means that the number 2 will be forced to a string, and the function will return the string “12”. The engine will backtrack to interpret the bytecode and update the type feedback.


I hope this article was helpful to you! 😊 Of course, there are a lot of things about the engine that I didn’t cover in this article (JS heap, call stack, etc.) that I might cover later. If you’re interested in the inner workings of Javascript, I highly encourage you to start doing some research on your own, V8 is open source, and there’s some great documentation on how it works in the background. 🤖

Thanks for reading and have a nice day! ❤

If you find any errors in the translation or other areas that need improvement, you are welcome to revise and PR the translation in the Gold Translation program, and you can also get corresponding bonus points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


Diggings translation project is a community for translating quality Internet technical articles from diggings English sharing articles. The content covers the fields of Android, iOS, front end, back end, blockchain, products, design, artificial intelligence and so on. For more high-quality translations, please keep paying attention to The Translation Project, official weibo and zhihu column.