JavaScript Internals: JavaScript engine, Run-time environment & setTimeout Web API

A detailed walk-through of all the core components that are involved in the execution of JavaScript code

Rupesh Mishra
Bits and Pieces

--

Photo by Bram Naus on Unsplash

In this article, we will discuss the internal working of JavaScript in the run-time environment and the browser. This will be a detailed walk-through of all the core components that are involved in the execution of JavaScript code. We will discuss the following components:

  1. JavaScript Engine
  2. JavaScript Runtime Environment
  3. Event loop, Event Table and Callback Queue or Message Queue
  4. setTimeout function internal working

Let’s begin with the JavaScript engine.

A small tip before we begin: Use Bit (Github) to turn your JS code into reusable and shareable components — start coding better and faster.

JavaScript Engine

JavaScript engines are inbuilt in all the modern browsers today. When the JavaScript file is loaded in the browser, JavaScript engine will execute each line of the file from top to bottom (to simplify the explanation we are avoiding hoisting in JS). JavaScript engine will parse the code line by line, convert it into machine code and then execute it. Some of the well-known JS engines are listed below:

Browser engines

JavaScript engine consists of two components:

  1. Execution context stack
  2. Heap
JavaScript Engine

Execution context stack (ECS)

Execution context stack is a stack data structure which follows the Last In First Out (LIFO) principle (the last item to enter the stack will be the first item to be removed from the stack). ECS stores the execution context for each function. Execution context is defined as an object which stores local variables, functions and objects. Primitive values like int, bool etc are stored inside the execution context object while function definitions and objects are not stored inside the execution context object, they are stored inside the heap. Execution context object just has the reference or memory address of where these function definitions and objects are stored.

JavaScript engine always executes the function which is at the top of the execution context stack.

By default, at the bottom of the ECS, we have a global execution context which deals with all the code in the global scope. Each function has its own execution context called functional execution context which gets inserted on the top of ECS as and when the function is called in the code. If the same function is called twice like in recursion, it will have two different functional execution context in the ECS.

When the execution of the function is completed, JS engine itself removes it from the ECS and starts executing the function on the top of the stack.

As JavaScript engine has only one ECS, it can execute only one thing at a time which is at the top of the ECS. This is what makes JavaScript single threaded.

Heap

Heap is a large unstructured data structure which stores all the dynamic data like function definitions, objects, arrays etc. Execution context stack just contains their reference or in other words stores their memory addresses where these function definitions, objects and arrays are stored. The memory occupied in the heap continues to exist even after the JavaScript code execution has completed. They are removed by the JavaScript Garbage Collector.

Let’s understand the heap and execution context stack using an example. Consider the code below:

Execution Context Stack example
Output: Execution context stack example
GIF: Execution context stack at each stage of the above code execution

When the browser loads the JS file, the JS engine will push the global execution context in the ECS and will start executing it. When JS engine reaches the function definition of functionOne, it stores the function definition in the heap memory and its reference in the global execution context. When functionOne is called by JS engine, it pushes functionOne execution context inside the ECS and starts executing functionOne, pausing the execution of the global execution context.

When JS engine calls functioninTwo inside functionOne, JS engine pushes functionTwo inside ECS and starts executing functionTwo, pausing the execution of functionOne. Once all the code inside functionTwo is executed, JS engine pop out functionTwo execution context and restarts executing remaining code of functionOne.

Similarly, it removes the functionOne execution context once all the code of functionOne is executed. It should be noted that even though functionOne has been removed from the ECS, objects and function definitions inside functionOne continue to occupy memory in the heap without being referred by any variable. They will be removed by the garbage collector automatically, we don't have to remove it ourselves. Once all the code from the global execution context is executed, ECS becomes empty and the execution of the script ends.

JavaScript Runtime Environment (JRE)

So far we have discussed JavaScript engine, but the JavaScript engine doesn’t run in isolation. It runs inside an environment called JavaScript Runtime Environment along with many other components. JRE is responsible for making JavaScript asynchronous. It is the reason JavaScript is able to add event listeners and make HTTP requests asynchronously.

JRE is just like a container which consists of the following components:

  1. JS Engine
  2. Web API
  3. Callback Queue or message queue
  4. Event Table
  5. Event loop
JavaScript Runtime Environment

We’ve already learnt about the JavaScript Engine. Let’s get to the other components in the list:

Web API

Web APIs are not part of the JS engine but they are part of the JavaScript Runtime Environment which is provided by the browser. JavaScript just provides us with a mechanism to access these API’s. As Web APIs are browser specific, they may vary from browser to browser. There may be cases where some Web APIs may be present in one browser but not in other.

Examples:

  1. DOM API for manipulating the DOM. document.getElementById,addEventListerner, document.querySelectorAll, etc. are part of the DOM API provided by the browser which we can access using JavaScript.
  2. AJAX calls or XMLHttpRequest. As Web APIs are browser specific and XMLHttpRequest is a Web API, we had to implement XMLHttpRequest in a different way for IE before JQuery saved us (remember?).
  3. Timer functions like setTimeout and setInterval are also provided by the browser.

When JavaScript engine finds any Web API method, it sends that method to an event table, where it waits till an event occurs. In the case of AJAX calls, the JS engine will send the AJAX calls to the event table and will continue the execution of code after the Ajax call. AJAX call will wait in the event table until the response from the AJAX call is returned. In case of timer function like setTimeout, it waits until the timer count becomes zero.

Event Table

It is a table data structure to store the asynchronous methods which need to be executed when some event occurs.

Callback Queue or Message Queue or Event Queue

Callback Queue or Message Queue is a queue data structure which follows First In First Out principle (item to be inserted first in the queue will be removed from the queue first). It stores all the messages which are moved from the event table to the event queue. Each message has an associated function. Callback queue maintains the order in which the message or methods were added in the queue.

Web API callbacks are moved from the event table to the event queue when an event occurs. For example, when AJAX calls are completed and the response is returned, it is moved from the event table to the event queue. Similarly, when the setTimout method waiting time becomes zero it is moved from the event queue to the event table.

Event Loop

Methods are executed neither in the event table nor in the event queue. They are executed by the JavaScript engine, only if it is present in the ECS. So, for the execution of any method, we need to move that method from the callback queue to the execution context stack. This is what the event loop does! Event loop continuously checks if the execution context stack is empty and if there are any messages in the event queue. It will move the method from the callback queue to ECS only when the execution context stack is empty.

We will understand all the above concepts with the help of setTimeout function so, let’s see what is setTimeout

setTimeout

setTimeout and setInterval are called as timer functions in JavaScript. They are used to schedule the execution of functions. They are not provided by the JS engine itself but they are provided by browser as part of the window object. Consider the window object

console.log(window)
setTimeout and setInterval functions on the window object

Here, we can see that both timer functions setTimeout and setInterval are available on the window object. As they are functions on window , value of this inside both the timer functions will always by window.

setTimeout function syntax

setTimeout(callbackFunction, timeToDelay)

setTimeout accepts callback function and time in milliseconds as a parameter. callbackFunction gets executed after the timeToDelay milliseconds (this statement will be discussed in detail later, it’s somewhat incorrect)

Example:

setTimeout basic syntax

Here, even though printStatement function has to be executed after 100 milliseconds but JS engine will not wait for 100 milliseconds for its execution to start but will execute the next line i.e. console.log without any delay.

Hence, the first statement to be printed is “I will be exected first” rather than “I will be printed after 100 milliseconds” though as per normal JS engine execution flow i.e. from top to bottom, “I will be printed after 100 milliseconds” should have printed first.

This shows that setTimeout does not block the execution of the script.

Consider another interesting example

setTimeout execution in zero milliseconds

In the above example, printStatement function is scheduled to be executed after zero milliseconds. We will expect printStatement to be executed immediately but we will be surprised to see the output. “Still, I will be executed first” will be printed first and then “I will be printed after 0 milliseconds” will be printed.

In the later part of this article, when we will discuss the internal functioning of JavaScript and setTimeout function, you will able to understand both the above setTimeout function behaviour

Passing parameter to setTimeout callback function

Cancel setTimeout callback execution

setTimeout returns a timer id which can be used to cancel the execution of setTimeout callback function

Example

clearTimeout to clean the timer

clearTimeout method is also available on the window object just like setTimeout method

Example to understand all the above concepts

Consider the code below

setTimeout example with waiting time 1000 ms

Output:

Output for setTimeout with waiting time 1000 ms

Execution context stack when the function functionOne is called will be as shown below:

GIF: setTimout in the JavaScript Runtime environment. Heap not shown in JS engine.

Let’s execute the above code line by line

Line 1: As soon as code loads in the browser, JS engine pushes the global execution context in the Execution Context Stack and starts executing the script.

Line 23: When functionOne is called, JS engine pushes functionOne execution context in the ECS and starts executing functionOne

Line 12: When JS engine encounters setTimeout, it moves setTimoutFunction along with metadata i.e. 1000 ms to Web API container or the event table. setTimeoutFunction will stay in the web API container till 1000 ms. JS engine doesn’t wait for 1000 ms for the execution of setTimeout callback function, it continues executing the code after setTimeout function

Line 14: JS engines iterate over the for loop 10000000000 times. This takes more than 1000 ms. Meanwhile, in the Web API container, when 1000 ms completes setTimeoutFunction is moved from the Web API container to the callback queue or the message queue. JS engine is unaware of these things, it does not know where is setTimeoutFunction.

During this period, the event loop continuously checks both the execution context stack and event loop. It checks two things:

  1. If the execution context stack is empty
  2. If there are any messages or events in the callback queue.

If the event loop finds that the execution context stack is empty and there is a message in the callback queue, it will move the associated method from callback queue to the execution context stack. Once this method is moved to the execution context stack, the JS engine will begin its execution. If there are multiple messages inside the callback queue, messages will be moved to the execution context stack one by one in the order they were added in the queue (Remember callback queue is First In First Out)

If the execution context stack is not empty, the event loop will not move the message from the callback queue to the execution context stack.

In the above example, while for loop is being executed by the JS engine there are two execution contexts in the stack — functionOne execution context and global execution context. Hence, setTimeoutFunction though present in the event queue will not be moved to the execution context stack by the event loop.

Line 21: Execution of functionOne has been completed by the JS engine, so the execution context of functionOne will be removed from ECS. Now, in ECS we have global execution context. Even though 1000 milliseconds have passed, setTimeoutFunction will still remain in the callback queue as ECS is not empty.

Thus, the time passed to setTimeout function does not guarantee its execution after the elapse of that time, but it is the minimum time after which setTimeout callback function will be executed.

Line 25: After the execution of this line, the global execution context will be removed from the execution context stack.

Now, when event loop checks execution context stack, it finds it is empty. Also, when it checks if there is any message in the callback queue it finds setTimeoutFunction. Both the condition to move the message from the callback queue to the execution context stack is satisfied hence, the event loop will move the setTimeoutFunction from callback queue to the execution context stack. As soon as setTimeoutFunction is moved to execution context stack, JS engine will begin its execution.

From the above example, we can understand the following things

  1. JavaScript is single thread i.e. at any instant of time it can execute only a single piece of code.
  2. JS engine executes the function which is at the top of the stack
  3. Asynchronous jobs like waiting for the setTimeout callback function to execute is not done by the JavaScript engine itself. It is done by the JavaScript run time environment
  4. Web APIs are moved from the Web API container to the callback queue only when the required event has occurred. For example, setTimeout callback function is moved to callback queue only when the time passed in the setTimeout has elapsed.

Now, consider our earlier example of setTimeout with zero milliseconds

In the above example, when JS engine finds setTimeout function in the code, it moves printStatement callback function to Web API container. As the delay is zero (0) milliseconds, printStatement is immediately moved to the callback queue. But as console.log(“Still, I will be executed first”) is not executed global execution context is still present in the execution context stack. Hence, printStatement will not be moved to the ECS. Once console.log(“Still, I will be executed first”) is executed, JS Engines removes the global execution context from ECS after which event loop will move the printStatement function to ECS and then “I will be printed after 0 milliseconds” will be printed.

Consider another example

setTimeout code for zero milliseconds waiting time

Output:

setTimeout with zero waiting time — output

Here, setTimeoutFunction will be executed only when all the code from the script has been executed and ECS is empty

Another setTimeout surprise output example

Consider the code below

To explain this output, we need two JavaScript concepts — closures and setTimeout.

We will be discussing closure in the next article but just to understand this example, we can say that child function, i.e. function inside another function, has access to all the methods, properties of the parent or grandparent function. Hence, all the functions in JavaScript have access to all the properties and method defined in the global scope.

Let’s try to understand the example

JS engine will iterate over the for loop four times and each timesetTimeout callback function will be pushed in the callback queue. When the value of ai will become four, for loop will be exited with 4 setTimeout callback function in the callback queue. Once, ECS becomes empty, all these 4 setTimeout functions will be executed one by one. But what about the value of ai ?

Just have a look at thewindow object

conosle.log(window)
ai variable on windows object

As shown in the above image, window object has a value of ai as 4. How is ai accessible outside the for loop ? We will discuss this in future articles.

After 1000 milliseconds, when all the setTimeout callback functions will be executed, they will have access to ai whose value is 4. Hence, all the 4 setTimeout callback functions will print the value of ai as 4

Conclusion

In this article, we studied about components of the JavaScript engine and run-time environment. On the basis of working of all the components, we understood the working of setTimeout function. We also understood some exceptional cases of setTimout function

--

--