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
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:
- JavaScript Engine
- JavaScript Runtime Environment
- Event loop, Event Table and Callback Queue or Message Queue
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:
JavaScript engine consists of two components:
- Execution context stack
- Heap
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:
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:
- JS Engine
- Web API
- Callback Queue or message queue
- Event Table
- Event loop
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:
- 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. - 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?).
- Timer functions like
setTimeout
andsetInterval
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)
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:
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
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
method is also available on the window
object just like setTimeout
method
Example to understand all the above concepts
Consider the code below
Output:
Execution context stack when the function functionOne
is called will be as shown below:
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:
- If the execution context stack is empty
- 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
- JavaScript is single thread i.e. at any instant of time it can execute only a single piece of code.
- JS engine executes the function which is at the top of the stack
- 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 - 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 thesetTimeout
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
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)
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
If you like my articles and find them useful, feel free to buy me a coffee. Thanks!
References:
- The Journey of JavaScript: from Downloading Scripts to Execution
- Execution context, scope chain, and JavaScript internals
- What the heck is the event loop anyway? | Philip Roberts
To get updates for my new stories, follow me on medium and twitter