| Note: This post is mainly based on the V8 engine used by Node.js and Chromium-based browsers.
The HTML parser encounters a
script tag with a source. Code from this source gets loaded from either the network, cache, or an installed service worker. The response is the requested script as a stream of bytes, which the byte stream decoder takes care of! The byte stream decoder decodes the stream of bytes as it’s being downloaded.
The byte stream decoder creates tokens from the decoded stream of bytes. For example,
0066 decodes to
n followed by a white space. Seems like you wrote
The engine uses two parsers: the pre-parser, and the parser. In order to reduce the time it takes to load up a website, the engine tries to avoid parsing code that's not necessary right away. The preparser handles code that may be used later on, while the parser handles the code that’s needed immediately! If a certain function will only get invoked after a user clicks a button, it's not necessary that this code is compiled immediately just to load up a website. If the user eventually ends up clicking the button and requiring that piece of code, it gets sent to the parser.
The parser creates nodes based on the tokens it receives from the byte stream decoder. With these nodes, it creates an Abstract Syntax Tree, or AST. 🌳
Next, it's time for the interpreter! The interpreter which walks through the AST, and generates byte code based on the information that the AST contains. Once the byte code has been generated fully, the AST is deleted, clearing up memory space. Finally, we have something that a machine can work with! 🎉
Although byte code is fast, it can be faster. As this bytecode runs, information is being generated. It can detect whether certain behavior happens often, and the types of the data that’s been used. Maybe you've been invoking a function dozens of times: it's time to optimize this so it'll run even faster! 🏃🏽♀️
The byte code, together with the generated type feedback, is sent to an optimizing compiler. The optimizing compiler takes the byte code and type feedback, and generates highly optimized machine code from these. 🚀
Say a certain function is invoked a 100 times and has always returned the same value so far. It will assume that it will also return this value the 101st time you invoke it.
Let’s say that we have the following function sum, that’s (so far) always been called with numerical values as arguments each time:
This returns the number
3! The next time we invoke it, it will assume that we’re invoking it again with two numerical values.
If that’s true, no dynamic lookup is required, and it can just re-use the optimized machine code. Else, if the assumption was incorrect, it will revert back to the original byte code instead of the optimized machine code.
This means that the number
2 will get coerced into a string, and the function will return the string
"12" instead. It goes back to executing the interpreted bytecode and updates the type feedback.
FAQ: I use Keynote to make the animations and screen record it lol. Feel free to translate this blog to your language, and thanks so much for doing so! Just keep a reference to the original article and let me know if you've translated it please! 😊