Something is running my JavaScript

Everything is a system. Engines are systems. JavaScript is run in an engine in a system in your browser.

Recently, I've been writing client-side code. Which means that web browsers download my code and run it, using their local machine's resources. That's obviously cool, but it's also mysterious to me.

I realized that I don't understand how browsers are doing this. Since I end up writing a good deal of CSS and HTML (well... JSX), I'm quite familiar with the DOM, but that's just one small part of the browser that I'm using when I make an app. What is it doing with my JS?

I thought about making this another exercise in figuring it out for myself (like I did with bucket sort), but I wasn't able to get very far: I figured that there is something that compiles JavaScript code into bytecode (and then to machine code), and that there are some standards that popular browsers use for interpreting and running JavaScript, so that applications work the same way in different browsers. But that's not really useful. I talked to cool person and current Recurser, J, about how I don't know what is running my code, and J said to think about how "everything is a system".

I'm still not exactly sure what that means. One of my initial thoughts was that maybe the browser is something like an OS. J liked that thought, but said it might be more helpful to think of the browser (or more specifically, the part running JavaScript) as a game engine. That seems like a helpful place to start, except that I don't understand game engines either. So let's start with a system that I can conceptualize a bit better.

How about cars -- they have engines? Care engines can be powerful, efficient, quiet, maintainable, etc... (and all of the opposites). You put gasoline in (or electricity). Then you provide it with inputs, such as pressing the gas pedal or changing the gears (the latter is now usually taken care of by an automated middleware). The engine responds by providing the appropriate level of rotation to the wheels' axles. You can swap the engine in your car out for another one, and it should work in a similar way. You could even use the engine for another purpose, although it probably won't be optimal for anything besides your car.

drivetrain

The engine running JavaScript code serves a similar purpose -- instead of gasoline, it uses the computer's hardware and power supply, and it takes inputs in the form of JavaScript functions and objects. Instead of powering wheels, it executes the functions and returns the results to the browser's display. And yes, there is more than one JavaScript engine -- different engines are used in different browsers and optimized for different devices. They all do things similarly, since they are all xecuting JavaScript code, but one may work better than others for a specific task or with specific hardware.

Javascript Engines

It turns out that Google is also fond of the engine analogy, and takes it a few steps further than I just did:

They built the V8 (get it?) JavaScript engine, which is used in Chrome (and in the default version of Node). They even named the interpreter part of the engine "Ignition" and the compiler: TurboFan. It used to have another compiler, named "Crankshaft", of course.

Diagram image

You can find out much more about V8 from the excellent Franziska Hinkelmann's post with JSCONF talk. And you can "peak under the hood" by passing --print-bytecode as an argument when you run node. This let's you seem the gnarly looking bytecode that V8 generates from your JavaScript. Here is the difference between defining a let and a const:

> let d = 2

[generated bytecode for function: ]
Parameter count 1
Frame size 16
         0x25e195be8e52 @    0 : 12 00             LdaConstant [0]
         0x25e195be8e54 @    2 : 26 fa             Star r1
         0x25e195be8e56 @    4 : 5e 55 01 fa 01    CallRuntime [NewScriptContext], r1-r1
         0x25e195be8e5b @    9 : 16 fa             PushContext r1
         0x25e195be8e5d @   11 : 0f                LdaTheHole 
         0x25e195be8e5e @   12 : 1d 04             StaCurrentContextSlot [4]
    0 E> 0x25e195be8e60 @   14 : a1                StackCheck 
    8 S> 0x25e195be8e61 @   15 : 0c 02             LdaSmi [2]
    8 E> 0x25e195be8e63 @   17 : 1d 04             StaCurrentContextSlot [4]
         0x25e195be8e65 @   19 : 0d                LdaUndefined 
   10 S> 0x25e195be8e66 @   20 : a5                Return 
Constant pool (size = 1)
Handler Table (size = 0)
undefined:

> const c = 2

[generated bytecode for function: ]
Parameter count 1
Frame size 16
         0x25e195be8c02 @    0 : 12 00             LdaConstant [0]
         0x25e195be8c04 @    2 : 26 fa             Star r1
         0x25e195be8c06 @    4 : 5e 55 01 fa 01    CallRuntime [NewScriptContext], r1-r1
         0x25e195be8c0b @    9 : 16 fa             PushContext r1
         0x25e195be8c0d @   11 : 0f                LdaTheHole 
         0x25e195be8c0e @   12 : 1d 04             StaCurrentContextSlot [4]
    0 E> 0x25e195be8c10 @   14 : a1                StackCheck
//highlight-start
   10 S> 0x25e195be8c11 @   15 : 0c 02             LdaSmi [2]
   10 E> 0x25e195be8c13 @   17 : 1d 04             StaCurrentContextSlot [4]
//highlight-end
         0x25e195be8c15 @   19 : 0d                LdaUndefined
// highlight-next-line
   12 S> 0x25e195be8c16 @   20 : a5                Return 
Constant pool (size = 1)
Handler Table (size = 0)
undefined

What exactly is the difference between 8S and 10S? Hell if I know -- look it up yourself!

For me, the biggest upside of looking into Javascript engines is learning about optimizations that I can make for most engines to run my code faster. One of the more interesting ones is that JavaScript engines optimize property access based on the object's shape. To optimize for this, you should always initialize your objects in the same way, so they don’t end up having different shapes. More here

That can't be all...

V8 (or whatever engine your browser uses) isn't all that's interacting with your code in the browser. As Philip Robert notes in my very favorite JS talk (which dives into the underlying structure of an event loop): If you clone the V8 code base and grep for things like setTimeout or DOM or HTTP request, they're not in there. They don't exist in V8. Which was a surprise to me.

Instead, the browser also has built in "Web APIs" (C++ APIs for Node) which, through an event loop (also implemented in your browser), push function execution onto the stack when it's clear. And these are hugely important. Not only do they allow you to write asyncronous code (through the call stack), they open up all sorts of user interactions that can be used programatically in your application. It's hard to imagine what you could even make without event handlers. And yeah, the DOM is a Web API too.

event-loop

So what runs JavaScript?

An engine (such as V8) that is built into the browser executes the functions on its stack. The functions can get pushed onto it either directly, or through an event loop that is (indirectly, through a task queue that I didn't dive into, since Philip Robert has that covered) used by Web APIs.

Systems all the way down.