How does JavaScript engine work?
Vijay Kumar
Posted on March 27, 2020
Have you ever been heard of execution stack? If your answer is no then you can follow along with this blog and if yes you can just review about execution context and execution stack. How does JS engine work? Let's find out the JS working mechanism together. We will have to find out what are the followings and how they work.
- Execution Context
- Execution Stack
- Global Execution Context
- Function Execution Context
- Hoisting
- Eval Execution Context
So, we are going to study all of the above topics in detail. Let's start...
1. Execution Context
Execution context is an environment or surrounding in which a specific type of JavaScript code is executed. Execution of JS codes is changed from context to context. Each execution has some particular properties and they share some similar properties too. There are three types of execution contexts
- Global Execution Context
- Function Execution Context
- Eval Execution Context
Each execution context store as a JavaScript object in the Execution Stack. You can see all of the execution contexts on the above image, in which the outer yellow box represents the global execution context. The pink ones are the function execution context and the light green one inside the test()
function is the eval execution context. Don't worry, we will explore these different contexts later. Just follow me. OK!🦾🐱👤
2. Execution Stack
As you know, a stack in computer science is like the stack of books, plates or maybe the doughnuts🍩 in the above cover photo. The stack is last-in-first-out (LIFO) i.e. that the book you just push
in the stack will be at the base of the stack and the book you put last in the stack will be easier to take out from the stack then the book at the base of the stack. So this nature is also the same for Computer Science. In execution stack, instead of books, the stack will contain the global context, function contexts and eval contexts. The stack keeps track of the current instruction being executed by the JavaScript engine or any compiler or interpreter. Firstly, the global context store in the stack which comes down to the base of the stack and the remaining function or eval contexts will take place in the stack on each other based on the order of your JS codes. When each function or eval is executed by JS engine, the function or eval context is popped (pop
) from the stack. push
and pop
are popular commands, in some programming languages, which are used to put or take data from the stack.
3. Global Execution Context
As you can see in the above image that any execution stack contain only one global execution context. Which type of codes executed in the global context? Of course, the codes which are not in any functions. When a JavaScript program is run, the global context is the first one which is pushed onto the execution stack.
let myName = `Vijay`;
let hobby = `painting`;
function display(myName,hobby) {
console.log(`My name is ${myName} and My hobby is ${hobby}.`);
}
/*
* Anything outside the functions which represent function execution context are
* all inside global execution context.
*/
4. Function Execution Context
When a function is executed in a JavaScript program, an object representing the function is pushed onto the execution stack. There are two phases in pushing the function execution context object. They are as follow:
- Creation phase
- Execution phase
Let's watch how the above two phases work behind the scene.
Creation Phase
In the creation phase, the function execution context object is created. If we further down the execution object, we can divide that into two environments.
- Lexical Environment
- Variable Environment
Without wasting time, let's see what they are.
Lexical Environment
The lexical environment is a representation or composition of identifiers (variable's or function's names) and the variables themselves, the reference to the primitive value or objects, in a specific structure. This environment also holds a reference to the parent lexical environment. We are going more deeper. Don't worry, I will summarise the whole article at the end and this gonna be the last detail we are diving in. So we can differentiate the lexical environment into three parts.
- Variable object (VO) or Environment record - a new object is created for the arguments passed to the function and it is called argument object. We can use the argument object when we don't know how many arguments are passed to the function. Then the function is view thoroughly by the JS engine to create a property which points out that function. For each local variable inside the function, a property is created with the
undefined
.
Please notice this place, we will reference later to this point in the HOISTING section of this article.
- Scoping and Scope Chain - every function declaration creates a scope ( like range or area ). All the arguments passed to the function and the variables declared inside the function are locked in the scope of that function. Global variables are the only variables that can be accessed anywhere in the code. Local variables are variables which are declared inside a functional scope. These local variables cannot be accessed from the global scope or their parent scope. If you want to know more about global and local variables, I will provide links to some articles. If your code tries to access local variables from the global scope, the JS engine will generate an error. When one or more functions are nested inside of another function, this nesting will create a scope chain. For example, if there is a function in which another function is nested. Then the scope chain for the nesting function will look something like this.
let myName = `Vijay`;
let hobby = `painting`;
const greet = `Hello`;
function display(myName,hobby,birthYear) {
function age(birthYear){
return 2020-birthYear;
}
console.log(`${greet}! My name is ${myName} and My hobby is ${hobby}.`);
}
/*
* Anything outside the functions which represent function execution context are
* all inside global execution context.
*/
For function age, its scope chain contains its local scope, its parent function's scope which is display
and the global function scope. If this function has to access a variable in global scope like greet
, then it will find the greet
variable in its local scope. If it didn't find the variable, then it will search one level up in the parent function scope. If it didn't find there too, then it will move to the global scope. This is called the scope chain.
- This binding - most novice programmers get confused with
this
keywords. Look at the following code statement.
//this.(method or function or property);
Actually when a this.
is found in the code, this
usually refers to an object in which scope it is called. When this
keyword is used inside a function, then this
will indicate to the global window object in the browser. If it is called using any method or property, then it belongs to a specific object in which the method is invoked. The this
keyword is not assigned a value until the function it is in is invoked.
class People(){
constructor(name,age){
this.name = name;
this.age = age;
}
display(){
// 'this' inside this method is not referencing to any object yet. It will
// reference to an object when it is invoked.
console.log(`My name is ${this.name} and I am &{this.age} years old`);
}
}
const vijay = new People('Vijay',19);
// Here, `this` from the display function reference to the vijay
// object
vijay.display();
function getThis(){
// 'this' in the statement point to the browser's window object which is in
// global scope
console.log(this);
}
/*
* Anything outside the functions which represent function execution context are
* all inside global execution context.
*/
Variable Environment
The variable environment and lexical environment are almost the same. The variable environment also has a variable object(VO), scope, scope chain and this
binding. There is only one difference after ES6. The lexical environment used to store function, let
and const
where the variable environment is for var
.
Execution Phase
During this phase, the codes are executed. The variables are assigned with their appropriate values and the functions do their jobs. Each of the functions pushed on the stack are executed in a first-in-last-serve manner. When a function is executed, its function execution context is removed from the execution stack.
5. Hoisting
All javascript developers should know the concept of hoisting. This is a new term born with ES2015. The actual concept is that when we declare a variable or a function in JavaScript we can use them before the declaration. Let me explain with some codes. We are going to refer the variable object (VO) section in this blog.
// Hoisting with a variable.
console.log(name);
/*
*The console output will be "undefined" because of the hoisting enables the
*variable available at the interpreting time.
*/
var name; // Declaration
name = "Vijay"; // Initialization
console.log(name); // Output will be "Vijay"
// Hoisting with function
age(1999,2020);
/*
* In the case of a function, the output will be "21" instead of "undefined"
*because the function performed its operation during interpretation.
*/
function age(birthYear,currentYear){
console.log(currentYear - birthYear);
}
age(2000,2020); // Output will be "20"
As in the above code and the reference from the VO section, we now know that when a variable is declared, it will be assigned with undefined
due to hoisting during the interpretation. When the variable is initialized with a value, then there will be no effect because of hoisting. Where a function declaration enables the program to use the function before its declaration statement in the code. These are all the effect of hoisting. In technical term, during interpretation, the variable declarations take places for those variables on the memory and this is same for function. That's why we can access the variables and functions before their declaration.
6. Eval Execution Context
The last but not least is the eval execution context. We are not going to do detail discussions about eval execution context because most programmers do not use this. It is obsolete. That means that it is still supported by browsers but it is a best practice not to use eval in any new project you are going to start. The actual function it does is nothing but only executed string which is just javascript code.
// Eval execution context
console.log(eval(`2+2`)); // the console output will be "4"
console.log(eval('2 + 2') === eval(new String('2 + 2'))); // the console output will be "false"
This is the end of our blog. I hope I provided as much knowledge I know about JS and you can read the following articles which will explain in more detail.
Further reading
The Ultimate Guide to Hoisting, Scopes, and Closures in JavaScript
eval() and eval execution context
Understanding Execution Context and Execution Stack in Javascript
Posted on March 27, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.