loading...
Cover image for ๐Ÿš€โš™๏ธ JavaScript Visualized: the JavaScript Engine

๐Ÿš€โš™๏ธ JavaScript Visualized: the JavaScript Engine

lydiahallie profile image Lydia Hallie Updated on ใƒป5 min read

JavaScript Visualized (7 Part Series)

1) โœจโ™ป๏ธ JavaScript Visualized: Event Loop 2) ๐Ÿ”ฅ๐Ÿ•บ๐Ÿผ JavaScript Visualized: Hoisting 3 ... 5 3) โšก๏ธโ›“JavaScript Visualized: Scope (Chain) 4) ๐Ÿš€โš™๏ธ JavaScript Visualized: the JavaScript Engine 5) ๐ŸŽ‰๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง JavaScript Visualized: Prototypal Inheritance 6) ๐Ÿ’ก๐ŸŽ JavaScript Visualized: Generators and Iterators 7) โญ๏ธ๐ŸŽ€ JavaScript Visualized: Promises & Async/Await

JavaScript is cool (don't @ me), but how can a machine actually understand the code you've written? As JavaScript devs, we usually don't have to deal with compilers ourselves. However, it's definitely good to know the basics of the JavaScript engine and see how it handles our human-friendly JS code, and turns it into something machines understand! ๐Ÿฅณ

| 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.

Alt Text


The byte stream decoder creates tokens from the decoded stream of bytes. For example, 0066 decodes to f, 0075 to u, 006e to n, 0063 to c, 0074 to t, 0069 to i, 006f to o, and 006e to n followed by a white space. Seems like you wrote function! This is a reserved keyword in JavaScript, a token gets created, and sent to the parser (and pre-parser, which I didn't cover in the gifs but will explain later). The same happens for the rest of the byte stream.

Alt Text


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. ๐ŸŒณ

Alt Text


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! ๐ŸŽ‰

Alt Text


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. ๐Ÿš€

Alt Text


JavaScript is a dynamically typed language, meaning that the types of data can change constantly. It would be extremely slow if the JavaScript engine had to check each time which data type a certain value has.

In order to reduce the time it takes to interpret the code, optimized machine code only handles the cases the engine has seen before while running the bytecode. If we repeatedly used a certain piece of code that returned the same data type over and over, the optimized machine code can simply be re-used in order to speed things up. However, since JavaScript is dynamically typed, it can happen that the same piece of code suddenly returns a different type of data. If that happens, the machine code gets de-optimized, and the engine falls back to interpreting the generated byte code.

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.

For example, the next time we invoke it, we pass a string instead of a number. Since JavaScript is dynamically typed, we can do this without any errors!

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.


I hope this post was useful to you! ๐Ÿ˜Š Of course, there are many parts to the engine that I haven't covered in this post (JS heap, call stack, etc.) which I might cover later! I definitely encourage you to start to doing some research yourself if you're interested in the internals of JavaScript, V8 is open source and has some great documentation on how it works under the hood! ๐Ÿค–

V8 Docs || V8 Github || Chrome University 2018: Life Of A Script


Feel free to reach out to me! Twitter || Instagram || GitHub || LinkedIn

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! ๐Ÿ˜Š

JavaScript Visualized (7 Part Series)

1) โœจโ™ป๏ธ JavaScript Visualized: Event Loop 2) ๐Ÿ”ฅ๐Ÿ•บ๐Ÿผ JavaScript Visualized: Hoisting 3 ... 5 3) โšก๏ธโ›“JavaScript Visualized: Scope (Chain) 4) ๐Ÿš€โš™๏ธ JavaScript Visualized: the JavaScript Engine 5) ๐ŸŽ‰๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง JavaScript Visualized: Prototypal Inheritance 6) ๐Ÿ’ก๐ŸŽ JavaScript Visualized: Generators and Iterators 7) โญ๏ธ๐ŸŽ€ JavaScript Visualized: Promises & Async/Await

Posted on by:

Discussion

markdown guide
 

Although you didn't cover the whole story of it, this is a decent explanation of how JavaScript engine works. All those gifs are amazing. They help my brain to solidify the theories. Thank you so much. I'm looking forward to your upcoming posts.

 
 

Agreed with the GIFs thing. They help heaps to understand what is going on. GIFs for the win!

 

Lovely animations! Thanks for a really clear article

 

The Gifs really pull the article together. Great stuff.

 

Thanks for the informative post. Your animations are superb! I am interested to know which software did you use to create your animations?

 

This is amazing!! I wish I'd had this at my coding bootcamp last year. I don't think it would have all sunk in at first but seeing it laid out visually would have helped things click at certain points as we learned more. I'm sharing it with them right now so future cohorts will be able to use this resource ๐Ÿ™Œ๐Ÿ™Œ๐Ÿ™Œ

 

Thanks!! ๐Ÿ˜ƒ Hope itโ€™ll help them!

 

This is brilliant and a great place to start for anyone exploring how JS works its magic. I know this had to have taken a lot of time and effort to create. Thank you for your efforts!!!!

 
 

This might not be important but,i just want you to know that this post was featured in the December 17th, 2019 edition of esnextnews curated by Dr Axel Rauschmayer and Johannes Weber.

Congrats!

 

Thanks you for all explanation. I've been looking for this information for a long time ^^.
But with what tool do you make these wonderful gifs ? :)

 
 

Apple Keynote? really? I did not know it was good to do that :-)

Haha yeah you can export it to animated gifs :)

 
 

I'm gonna call you a TV series cause you've left me hangin' on a cliff. Great, great write-up and, I think I speak for everyone when I say we want more. Neh, we need more. For example, regarding "Say a certain function is invoked a 100 times...", I'm curious what the magic number is and how it was determined. Is it always 100 or does the compiler determine at run-time? And, armed with that information, how can we write more efficient JavaScript code?

 

Thank you Lydia for this amazing explanation!
I am a language without a compiler but I have an engine (called sometimes a compiler). This engine is a place where 4 of my sons are playing. The first one called "Byte Stream Decoder" which creates tokens. These tokens are sent to the second one called "Parser" which creates nodes. These nodes are parts of the third one called "Abstract Syntax Tree". The last one called "Interpreter" ends the game by climbing the tree and getting the disk containing Byte Code. I forgot, I have also a daughter called "Inline Caching" which takes care of this disk and try to optimise it. I am JAVASCRIPT!

 

Thanks for the great and easy-to-understand article on JavaScript Engine internals. The animations are amazing!

I've translated your article in Korean:
betterweb.or.kr/blog/%eb%88%88%ec%...

 
 

Nice post. Very comprehensive. I shared it on our JS group

 
 
 

Hello Lydia, do you mind to share what tools you've used to create such visualizations?
I was thinking about such animations for some meetups I conduct from time to time.

I would really appreciate it, thank you!

 

One question I had in mind while reading this post was why not ship the pre-parser and parser to the world so devs can generate a more optimized version directly in their build system, and send that to the browser? Then I remembered about wasm which I guess it kinds of does exactly that, but more general and doesn't support javascript.

 

Great post!! Very detailed, and I like your writing style. I appreciate the examples, like when you brought up a function being run 100 times and then getting passed through the optimizing compiler, I wish more writers would use that with programming explainers. Looking forward to reading more of your posts.

 

In reference to your inline caching example, if you had a function that was called every other time with different data types (โ€œsumโ€ could be called with numbers, then strings, then numbers again repeatedly), wouldnโ€™t the performance of having to update the type feedback be nullified? If so, how is this handled in the V8 engine? Thanks!

 

Lydia hi!

This is a very good project.I always wanted see a some kind of visual presentation for programming languages.I am new to programming and i have many questions in general but this helped me a lot.I hope to continue doing this for javascript or for another language.
Thanks.

 

Thanks for the overview!

Question for you and the group: at what point in this process are scopes and lookup tables created for the various variable bindings and the values the hold? ๐Ÿค”

Since variable bindings and their declared values need to make it into the byte code, I would think this happens prior to running the interpreter. And it seems like that process would require access to either the raw tokens created by the byte stream decoder, or the AST generated by the parser, because the code needs to be 'crawled' in a meaningful way so as to identify the bindings that have been declared.

Can anyone clarify for me?

 

The visualizations make it so easy to understand! What tool did you use for that?

 

Thanks, it's a good intro to the V8 which sometimes we see as a distant complex obscure thing.

 

Thank you Lydia, that was for me, as an Java developer, the exact amount of information and lovely illustration needed to get an overview how the Javascript engine works.

 

Very good job on this article! You broke it down to really easy to understand bits!

This brought up a question, and assuming I am understanding this correctly. There are parser nodes that signify a group of tokens that represent a single parser. For example, a single parser contains tokens about a single function.

With that said, what is faster? Having your JS code that is all in a single parser with more tokens or more parser's with less tokens?

I'd assume it depends on the case of how many parser's there are, because you could theoretically search for parser's in O(nlogn), with O(n) tokens, but at some point, if there are so many parser's, a single parser with more tokens would make sense.

 

Thanks for the great article! I translated into korean and here is the link

 

How come I missed all other visualized articles... amazing work!

 

Great to see you on the platform!! You are one of the few people that I really like to enjoy reading. I'm already checking all of your other posts here :) I'm anxious to read your upcoming posts

 
 
 

I had a compiler structures class at University before. It seems some point you described bring me back to that memory.

 

Awesome post! ๐Ÿ˜ƒ๐Ÿ‘ Thanks for sharing!

 

What a clear explanation of JavaScript engine and how it works ๐Ÿ˜ฎ

Good post!