But how does JavaScript actually work? ๐Ÿ™‹ (JS ep.3)

But how does JavaScript actually work? ๐Ÿ™‹ (JS ep.3)

JS Runtime baby

ยท

5 min read

As I begin my JavaScript journey, it has became clear I have no idea how JavaScript actually works behind the scenes. In this article I attempt to learn and describe the working components of the JavaScript Runtime of the browser (this is different from the runtime on node!).

I took a lot of information and inspiration from Philip Roberts and his 2014 talk: youtube.com/watch?v=8aGhZQkoFbQ

The components of the JS Runtime

The JS Runtime is made up of the following components:

  1. The JavaScript Engine
  2. Web APIs
  3. The Callback Queue
  4. The Event Loop


1. The JavaScript engine

Each browser has its own version of the JS Engine that executes JS code. Parsing code is converting human readable code into machine code, the 0's and 1's that all computers process. The most used and well known JS Engine, is V8 by the Google Chromium project.

The simplified process of the JS Engine is as follows: JS Code -> Parsing -> Compilation to machine code -> Execution

The JavaScript Engine is comprised of 2 parts, The Heap & The Call Stack

The Heap

Where non-primitives are stored in memory. Sometimes called the 'memory heap'.

const nuts = {
  best: 'cashew',
  worst: 'macadamia'
}

^ We are telling the engine to allocate memory for the nuts object and its values. Any time I call the nuts object, it will point to a region in our memory heap that has the best and worst keys pointing to cashew and macadamia.

The Call Stack

A data structure that tracks where we are in the code so we can run the code in order. Running in a Last-in, First-out manner, each entry in the stack is named a 'stack frame'. Running top to bottom, the JS Engine processes each function in order.

When the JS Engine steps into a function, it is 'pushed onto' the stack. When a function returns a value or gets sent to the Web APIs, it is 'popped off' the stack. When a function does not explicitly return a value, the engine will return undefined & pop the function off the stack.

This top to bottom manner of the call stack is what gives JavaScript it's single-threaded characteristic, and why JavaScript runs synchronously.

Some JS Engines will store simple variables on the stack, and complex data structures, arrays, objects, stored on the heap.


2. Web APIs

Provided by the browser itself, these are a large number of APIs available to us with various types of functionality.

Some common types are:

  • The DOM!
  • Canvas
  • Fetch API

Features like event listeners, timing functions and AJAX requests all sit in the Web APIs container until an action gets triggered. When a request finishes receiving its data, or a setTimeout reaches its time, or a click event happens, this triggers a callback function to be sent to the Callback Queue.


3. The Callback Queue

The Callback Queue stores callback functions sent from the Web APIs in the order in which they were added. This queue uses a First In, First Out principle. Surprisingly (to me) the Callback Queue uses the array push method to add a new callback function to the end of the queue and the array shift method to remove the first item in the queue.

Callback functions will sit in the queue until the Call Stack is empty, they are then moved into the Call Stack by the Event Loop.


4. The Event Loop

The non-blocking event loop monitors the state of the Call Stack & the Callback Queue. If the Call Stack is empty, it will grab a callback from The Callback Queue and put it into the Call Stack.

This is why JavaScript often gets described as being able to run asynchronously, even though it is a single-threaded language. While the JavaScript Engine can only execute one function at a time, it can push callbacks from the Web APIs to the Callback Queue and in turn, the event loop can constantly add those callback to the Call Stack. So in otherwords, the JS Engine is working at the same time as your Web APIs, giving us concurrency! Awesome!

A cool example of the JS Runtime

console.log('Hello');
setTimeout(function callback() {
  console.log('World');
}, 0);
console.log('Donkey')

Due to the nature of the JS Runtime, this will output as:

Hello
Donkey
World

Why? Because even though the setTimeout is set to 0 it is a Web API in the browser and does not live in the JavaScript Engine. Instead, after completing in 0 seconds, it is added to the Callback Queue and made to wait until the Call Stack is clear, before being added and executed. This works the same way for Ajax requests, click events etc


Go and test it yourself!

I took a lot of information and inspiration from Philip Roberts and his 2014 talk: youtube.com/watch?v=8aGhZQkoFbQ - The crazy bastard has even built a tool to play with and witness the JS Runtime at runtime, http://latentflip.com/loupe/, and it's really bloody amazing.


My thoughts

It's pretty interesting to actually understand what's going on. I can think of many instances of setTimeout giving me grief over the years, now I understand why. I imagine this knowledge will be helpful in many ways in the future that I cannot foresee right now, and I am happy I took the time to gain a better understanding. I know ill be back to re-read this more than once. If anyone is still reading this, I'd love to hear your thoughts about the JavaScript Runtime, or anecdotes of times the knowledge has been helpful.

ย