“You have to know where you were going in order to get there.” ― Suzanne Weyn
What is a Runtime Environment?
In order to run a computer program written in a programming language, there should be many processes and steps involved, the program should communicate with the hardware, OS, and also complex tasks such as managing the heap and stacks, memory management, garbage collections, and many more.
This process does not handle by the programmer and this is a very dynamic process that differs from program to program. A runtime environment provides the ability to manage the above process and run the programs on different platforms.
Every browser comes with a specific JS Runtime engine and does not need to install separately, those are
V8 Engine - Used in Google Chrome web browser and NodeJS. Developed using C++.
SpiderMonkey - Used in Firefox and developed using C++.
Nitro - Used in Safari web browser.
Chakra - Used in Microsoft Edge web browser.
At first when a JS program starts to run it runs in an execution context. There are two execution contexts
- Global Execution Context
- Functional Execution Context
Each of these execution contexts contains two main phases of program execution, those are.
1. Creational Phase
- Creating an activation object by scanning the JS code and list down variables and declarations.
- Creating a Scope Chain - This will list down all the variables in the current function.
- Creating global variable “this”. (“window” for client-side JS)
2. Execution Phase
- Code execution starts at this phase, Values will be assigned to variables listed down in the creational phase. If it’s a function it will execute in a separate functional execution context.
Let’s now look at how this works inside.
In the above program, we define two variables at first and a function that concatenates two strings and another variable that holds the results of the returned value of the function.
Let’s now see how this program works using a simple diagram.
As you can see at the creational phase the values will be undefined but when it comes to the execution phase the values for each variable will be assigned and for each function, it creates a separate “Functional Execution Context”. And after execution of the function, the value will be returned and the “functional execution context” will be destroyed.
Now let’s assume there are recursion functions or there are functions that might call other functions inside it. At that time JS engine should keep track of the order of the function execution this will be handled by the “Call Stack”.
Here’s a simple example of how it works
For the above function, the call stack will be like this.
However, it is important to be aware that when calling functions inside another function and recursive functions it is important to follow best practices of coding otherwise the functions keep creating functional execution context inside its parent’s functional execution context and this will leads to a stack overflow error.
In the earlier application, we saw that in the creation phase the variables in the global execution context will be defined as “undefined”.
The process happens as follows when the application loads to the JS engine it first lists out all of the variables in the global execution context. and move all into the top, but it does not assign the real values, this concept is called Hoisting.
But this behaves in different ways in different cases.
There are certain ways of defining variables those are var, let, and const. when it comes to the hoisting. Now let’s see an example using a simple program.
If we inspect those variables individually (in developer console or running on NodeJS) the result will be as follows.
Line 1 - undefined
Line 2- ReferenceError: Cannot access ‘b’ before initialization
Line 3- ReferenceError: Cannot access ‘c’ before initialization
As we can see the program has identified that there is a variable named “b” and “c” which means hoisting works for all three ways of variable declaration types. But when it comes to the const and let it throws an exception.
The reason for that is in the JS engine variables defined as const and let are stored in the Script Scope and meanwhile, the variables defined as var stored in the Global Scope.
and JS engine do not provide access to the script scope. in order to access the variables defined using let and const should be initialized with a value at first.
Hoisting also works with the functions, There are two ways of defining functions for both ways hoisting acts differently. Let’s look at an example of how hoisting behaves with functions.
In the above program, we have defined functions, one is a function (add), an arrow function (multiply) and the other one is a function reference (divide). The output will as below.
answerOfAdd : 30
answerOfMultiply: throws an error “ReferenceError: multiply is not defined”
answerOfDivide : throws an error “TypeError: divide is not a function”
As per the results, we can see when it comes to the function it only supports the functions not for arrow functions neither function references, but hoisting works for both of those.
This article was inspired by the following videos which explaining and visualizing how JS runtime engine and hosting works with a practical approach.