How does the JS engine work?
The HTML parser encounters a script tag with a source. The source code inside this script gets loaded as a UTF-16 byte stream to a byte stream decoder. This byte stream decoder then decodes the bytes into token which are sent to the parser. To save time, the engine tries to avoid parsing code that’s not necessary right away.
The parser creates nodes based on the tokens it receives. With these nodes, it creates an Abstract Syntax Tree (AST).
Here you can check out how your code gets transformed to AST:
Next up is the interpreter, it walks through the AST and generates byte code. It reads the code line by line. When the byte code has been generated, the AST is deleted, clearing up memory space.
The problem with interpreters is that running the same code multiple times can get really slow, that’s why we have the compiler which does not repeat loops and is more optimized.
The Profiler monitors and watches code to optimize it.
The compiler works ahead of time and creates a translation of the code that has been written and compiles down to a lower level language that machines can read.
Other examples of compilers are Babel, which converts modern JS to browser compatible JS, and Typescript, which is a superset of JS that compiles down to JS.
6. Optimized code
Why do you need to know these things? So we can optimize the code to be run quicker.
Call stack and Memory Heap
The call stack keeps track of where we are in the code. It uses first in and last out and stacks for example functions. The memory heap is a place to store and write information, where we allocate, use and remove memory. The callstack calls a function from the memory heap and after executing removes it from the stack. When the maximum call stack has been reached, e.g. with an infinite loop, we call it a stack overflow.
JS automatically frees up memory that no longer is used. It marks and sweeps it from the memory. For example when a value gets reassigned and the original value is not used anymore. We call this garbage collection.
Memory leaks happens when a piece of memory is no longer being used or is required by an application but still is taking memory. This happens for example when you:
- Accidentally set global variables
- Don’t close eventListeners or setIntervals
- Reference out of the DOM
Event loop and Callback Queue
If the call stack does not know what to do with a function it will send it to the Web API. When it is ready, for example when the data has been fetched, the function gets send to the callback queue and then the event loop pushes it onto the call stack only when the call stack is empty.
Here is a great resource where you can see it all in action:
JS is a single threaded language which means that only one set of instructions can be executed at a time. There is only one call stack. Except behind the scenes we do not only have the JS Engine but also there is the JS Runtime. The runtime is the whole package including all the tools, the engine is responsible of composing JS.
The browser comes with the web API which is not native to JS. The web API can take care of asynchronous actions. You can access the web API by using methods on the
window object. If we look at the Node.js, which is a runtime that enables us to run JS outside of the browser written in C++, it uses the V8 engine from the browser but extends it with a global API instead of the web API the browser uses.
Global execution context
Every code in JS is run inside an execution context. The global execution context is the first to run being pushes from the call stack. It gives access to the global object and the this keyword and is created in the so called creation phase.
This article will get updated from time to time.
If you’ve learned something from this article please leave a clap.