The Code that Runs Your JavaScript Code

What is the JavaScript Runtime Environment, and how can you apply knowledge of it to your job as a web developer?

Alex Zito-Wolf
May 25 · 8 min read

How often do you think about the environment that your code runs in, and the resources that are available to it when it runs? Understanding the ‘runtime’ environment can help us make better choices as JavaScript developers when writing code.

When you take a script in your editor of choice and you hop into your terminal and type node test.js, what is really happening to allow your code to execute? The runtime environment is spun up for you in a split second, defined primarily by the “V8 Engine” and a few additional constructs know together as the “JavaScript runtime”. It applies the collective wisdom of 20 years of the worlds finest engineers to find the absolute most efficient way to run your puny little script, eventually logging “hello world” inside your REPL loop.

Image for post
Image for post

Synchronous Execution (complexity level 1)

The heap is responsible for saving the state of the functions and variables that you define in your script, and I will essentially ignore it in the following code examples this is so that I can better focus this talk on the topics of asynchronous programming and the event loop.

So let’s take the following script:

const bar = (arg) => { return arg }
const foo = (arg) => { return bar(arg) }
foo('function call')

The first thing that the runtime will do is break down your JS source code into chunks of executable subroutines, called stack frames. Stack frames are a concept commonly used in CS . The easiest way to think about a stack frame is as follows:

Each stack frame corresponds to a call to a function or procedure which has not yet terminated with a return. For each function call, a frame is created containing function's arguments and local variables. Credit

The above script will generate 2 separate stack frames, the first contains foo’s argument and local variables. When foo calls bar, a second frame is created with the arguments and local variables of bar. Local variables here included the function bar. When bar returns, that stack frame is popped from the stack and the foo frame will return, finally clearing the stack. The call stack is a LIFO data structure, which means the last thing frame added to the stack will be the first on to be executed.

The environment feeds stack frames into the V8 Engine one by one for execution, which subsequently executes those stack frames, or hands them off to other APIs that are available in the runtime.

Single Threaded Execution

When you blow up your program you can see a very single stack trace, which is actually a freeze-frame of the state of the call stack when you blew up your code, what the call stack looked like at the moment that the error was caught.

Image for post
Image for post

The term ‘blowing the stack’ is a term for a different type of exception when the callstack becomes so large that the runtime gives you this message (say, if you call a function recursively).

Image for post
Image for post

This segways us to the next level of complexity, the paradigms in the runtime for allowing “non-blocking IO” or writing code that allows the stack to be clear or free to execute as quickly as possible.

Async / Non-Blocking Execution (complexity level 2)

To examine the call stack, let’s take the following example:

This script will follow a different path than the previous synchronous script example. It will utilize the other constructs in the browser runtime to help make sure that the program can run in such a way that is ‘non-blocking,’ or in other words, frees up the stack to allow the program to continue executing functions. In this case, we will take the following path to execution:

  1. Before anything moves into the stack, the immediateExecution will be saved in the heap for later use
  2. The function ajax will be pushed to the top of the call stack
  3. Then the function ajax will execute, calling the http Web API (essentially a function defined by the browser — the full list can be found here)
  4. The web API registers a callback function in the callback queue that will run when there is a return of data from https://example.com/api.
  5. The function is then removed from the call stack, clearing the stack for execution of other important functions
  6. The function immediateExecution will be added to the call stack, and will run, returning ‘1000000’ after 500ms or so
  7. The HTTP request API in the callback queue finishes
  8. the event loop pulls the callback function back into the call stack
  9. the anonymous function in the ajax call runs, console logging the response variable

Ajax functions and other http requests are designed to run asynchronously as described above. If we ran the same script synchronously, then we would delay the running of immediateExecution and clog the main thread. Take the following edits:

credit: https://blog.sessionstack.com/how-javascript-works-event-loop-and-the-rise-of-async-programming-5-ways-to-better-coding-with-2f077c4438b5

The above code tells the environment to actually keep that AJAX request on the main thread, which will pause code execution until it returns, 500–1000ms later. Only then will the immediateExecution function be able to start execution.

Let’s look at a fun dynamic GIF example, from the excellent article by Alexander Zlatkov to help demonstrate async execution.

Given the code block below:

The environment state will go through the 16 states below:

Image for post
Image for post

You can see very clearly from this example how the Web APIs and the callback queue work together to move code through the queue efficiently. As you add more callbacks to the queue, there is a predictable method for executing them on the single thread.

Note: Node.js provides a very different runtime environment, though it is also powered by Google Chrome’s V8 JS Engine. Node.js will not provide for you the DOM tree, AJAX, or other Web API’s. However, Node.js has a similar paradigm to these APIs. Node.js APIs are defined here.

The Logic of the Event Loop (complexity level 3)

The main components that I leave out above are rendering cycles, which run at regular intervals in-between stack frames executing on the stack to repaint the UI of your application, and the difference between micro and macro tasks, which are injected into the callstack at slightly different times by the event loop cycle. I hope to write about those concepts in a following post, but if you are curious I have linked off to a few of my favorite resources on those topics.

Writing Runtime-Environment-Aware JavaScript

Non-blocking IO

For large, CPU-intensive processes, use additional web workers to spawn additional threads that can perform operations. Worker threads are still an experimental feature, meaning using them on production environment probably isn’t the best idea. Here’s an example of code defining a web worker, and here is a simple explanation of how to build a web worker.

Compilation

  1. Dynamic properties: adding properties to an object after instantiation will force a hidden class change and slow down any methods that were optimized for the previous hidden class. Instead, assign all of an object’s properties in its constructor.
  2. Methods: code that executes the same method repeatedly will run faster than code that executes many different methods only once (due to inline caching).
  3. Arrays: avoid sparse arrays where keys are not incremental numbers. Sparse arrays which don’t have every element inside them are a hash table. Elements in such arrays are more expensive to access. Also, try to avoid pre-allocating large arrays. It’s better to grow as you go. Finally, don’t delete elements in arrays. It makes the keys sparse.

Sources

How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding with async/await by Alexander Zlatkov

The Javascript Runtime Environment…and, you know, how Javascript works by Jamie Uttariello

About the V8 Engine

JavaScript Engines — How do they Even? JSConf EU, Franziska Hinkelmann

Breaking the JavaScript Speed Limit with V8 Google I/O 2012, Daniel Clifford

About the Event loop

The Event Loop, Wikipedia

Concurrency model and the event loop, Mozilla

JavaScript In Plain English

New JavaScript + Web Development articles every day.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store