You write some JavaScript code. You open the browser. You see what you wrote come to life. Seems like magic, right?
But, of course, we know magic doesn't truly exist - behind every framework, library, array method et cetera are the gears that turn the wheel. Let's take a look into the inner workings of how JavaScript is executed in modern browsers.
First, let's understand the difference between a compiled language and an interpreted one. In both cases, the goal is to take a human-readable language and translate it into machine language. Keep in mind that these are simplified, general distinctions. There are more nuanced differences, even within distinctions themselves. It's worth noting that a compiler can be written for any language.
Before being executed, a compiled language goes through a preliminary step - a "build" step. All of the code is translated at once and then it can be run on the target machine. For this reason, a compiled program can only be run on the machine it was compiled for - the translation for MacOS β the one for Windows. Although this seems like a downside, compiled languages often result in a faster program and a lot of trivial bugs can be caught at compile time as opposed to run time. A few examples of compiled languages are Java, C, and C++.
Interpreted languages, on the other hand, are interpreted line-by-line at run time by an interpreter. This allows for a little more flexibility - as long as the machine has an interpreter for the language installed, it'll be able to run. As a downside, programs in interpreted languages can be less performant and more susceptible to bugs. A reason for the former is that in compiled languages, some re-used code (such as a function) only needs to be translated once whereas an interpreter will re-translate. Some examples of interpreted languages are Python, Ruby, and our old pal JavaScript.
JavaScript, our little web workhorse, uses a hybrid method in modern JS engines. I know what you may be thinking β didn't I just list it as an interpreted language? Well, it still is, but we programmers like our efficiency. As mentioned above, a compiler can be written for any language. In comes the JIT.
Please note that these explanations are very simplified. I highly recommend further reading if you want to learn more about what happens under the hood (sources listed below).
JIT stands for just-in-time compilation. In a similar vein as being interpreted line-by-line on the fly, the code is compiled as the program is running, rather than going through a full compilation step before being executed. To go further, as opposed to the all source code being translated & optimized into the target's machine code, JIT compilation goes through several steps to optimize the code as needed, resulting in programs that are fast to start and retain optimization as they run. Code is first simply interpreted and, during execution, "warm" segments that are run several times are sent to a base compiler and "hot" segments that are run a significant number of times are sent to an optimized compiler.
While that is the basic idea, the nitty gritty of JavaScript engines are more advance and utilize different concepts in different places. I'm going to use Chrome's V8 engine (post 5.9 with Ignition & Turbofan) as a baseline, but keep in mind that other browsers' engines have slight differences in implementation. For a brief introduction, V8 is an open-source JavaScript engine written in C++ that compiles JavaScript to optimized machine code.
First, the source JS code goes through a parsing step that creates an Abstract-Syntax-Tree (AST) representation of the code. The AST is then interpreted into unoptimized bytecode (Ignition's responsibility) and executed. This allows programs to get up and running quickly. The bytecode is then selectively optimized by Turbofan based on feedback from Ignition. The specifics of how the optimizations are made can be difficult to explain succinctly. However, as with all optimization, at the heart it aims to reduce unnecessary repeated actions and generally improve efficiency of tasks.
Gaining a deeper knowledge of JavaScript engines can lend to better program design and practices to optimize efficiency. For further reading, take a look at these resources:
- Interpreted vs. Compiled Languages
- Crash Course in JIT Compilers
- How JavaScript Works / optimizing for the V8 Engine
- An Introduction to Speculative Optimization in V8
Again, a lot of this explanation simplified for sake of brevity. Let me know if there are any concepts that could benefit from being more in-depth!
Top comments (4)
I think this type of writing is tremendously valuable. It is really easy to look at the browser as a blackbox; diving into the details helps to demystify it. I'd love to see more content like this. :)
Are there any resources where i could pass in a JS snippet in a string or pass a file to igniton and turbofan and compare the output? That'd be awesome if such a tool exists.
I haven't found such a thing yet! But this article does a pretty good job of explaining how the speculative optimization works. Also worth checking out the docs for them - ignition / turbofan. The V8 blog is really well written!
Great read, thanks for the write up and the links!