javaScript Behind the scenes
Coding is quite easy.But writing good code and understanding how that code works is much harder. I don’t mean to discourage you because it’s hard. Actually, understanding how the code works is the main thing. Once you understand how the code works behind the scenes, you can write code in any way that suits you. JavaScript typically runs in any environment. Most often, it could be our widely used browser or another application program like Node.js. Now, wherever this JavaScript runs, there is a JavaScript engine (Google V8 Engine, Spider Monkey, JavaScript Core, etc.), whose job is to execute this JavaScript code. Browsers use different engines to run JavaScript. For example, Google Chrome uses the Google V8 engine, Firefox uses Spider Monkey/Rhino, Internet Explorer and Edge use the Chakra engine, and Safari uses JavaScript Core/Nitro. There are many JavaScript engines, and for a full reference, you can visit www.web-w3.com. Now, this JavaScript code gets executed and runs in several steps. We can see this from the following
CODE
PARSER Reads Code Line by line
Convert
Them to Machine code
CODE RUNS
Abstract Syntax Tree
Here, the code first goes through a parser. Generally, syntax errors are checked here. If any syntax error is found, it is thrown from here. And if all the syntax is correct, then a data structure called the Abstract Syntax Tree is created from here.
In the next step, this code is converted into machine code, which our computer can actually understand. Finally, our code is run.
Why do we need to go through so many steps before running a code? Yes, our computer basically cannot understand anything other than machine code. No matter which language we write the code in, it must be converted into machine code; otherwise, our computer cannot understand it.
Execution Context: When the JavaScript engine starts reading your code:
- Before running any code, a global execution context is created.
- Then, when a function is executed or called, another new execution context is created.
- Each execution context gets its own this keyword. The this keyword refers to the leading object where it is located. There’s no need to worry about this for now. It’s discussed in detail in the advanced chapters.
Now, what is an execution context in JavaScript? An execution context in JavaScript is essentially a concept that holds information about the environment in which the current code is being executed. Before the JavaScript engine executes any code, it creates a global execution context. Additionally, a new execution context is created each time a function is executed. The global execution context is nothing special; it’s just something the engine creates by default. In a browser, the global execution context is the window object. Declaring a global variable or assigning it to the window object is the same thing.
var a = 10;
Now, if we check if `a` is within the window object, we can see:
window.a // 10
This shows that it’s the same value as the declared `a`:
> a
10
> window.a
10
> a === window.a
true
So, declaring a global variable or assigning it to the window object is the same thing. However, if your environment is not a browser, the global execution context can vary depending on the environment. For instance, in Node.js, the global execution context is the global object. This can change depending on the environment.
Hands-on JavaScript: A new execution context: When a new execution context is created due to a function call, the JavaScript engine takes some time to configure it. When a function is called, there are two phases:
- Creation phase
- Execution phase
1. Creation Phase: In this phase, the variable object is first created. Then the scope chain is created (scope will be discussed later). Finally, the value of the this keyword is determined. If you are not familiar with `this`, don’t worry. Just keep in mind that its value is determined here, and I will discuss `this` later. During the creation phase, the following occurs:
- An argument list is created for all arguments passed to the function.
- The code scans all functions and stores each function in a variable object, which typically points to the function.
- The code looks for variable declarations and sets the value to `undefined` for each declared variable.
2. Execution Phase: In this phase, the code is executed. The execution context maintains the code, executing it line by line. This is why calling a function before defining it works correctly. Similarly, using a variable before declaring it will set its value to `undefined` and not show any error.
Due to this behaviour of the JavaScript engine, you might see some surprising things. You might have noticed that in JavaScript, you can call a function before defining it, and the code will work completely fine:
first();
function first() {
console.log(‘Hello’);
}
Running this entire code together will work correctly. Similarly, you can use a variable before defining it (if you use it before defining, its value will be shown as undefined, and no error will be shown):
console.log(welcome);
var welcome = ‘Hello World!’;
The output will be like this, but no error will occur:
> console.log(welcome);
undefined
> var welcome = ‘Hello World!’;
Why is this happening? The reason is simple. During the creation phase, the engine already knows where everything is declared or whether it is declared at all, and hence it works correctly.
Execution Stack: We now have a clear understanding of the execution context. Simply put, a new function call means a new execution context. But let’s say we have three function calls. How many execution contexts will there be? There will be four execution contexts (including the global execution context). How do these three execution contexts work together? To understand that, we need to learn about the execution stack.
To get a clear understanding of the execution stack in JavaScript, let’s use an example and see how the code is executed:
var name = ‘Robin’;
function first() {
var welcome = ‘Hello’;
second();
console.log(welcome + name);
}
function second() {
var welcome2 = ‘Hi!’;
third();
console.log(welcome2 + name);
}
function third() {
var welcome3 = ‘Hey!’;
console.log(welcome3 + name);
}
first();
If you run this code, it will execute in the following order:
Hey! Robin
Hi! Robin
Hello Robin
The later ones appear first, and the earlier ones appear later. It seems a bit jumbled, doesn’t it? To understand how the execution stack works, look at the following diagram:
var name = ‘’Robin’’;
function first() {
var welcome = ‘Hello’;
}
second();
console.log(welcome + name);
}
function second() {
var welcome2 = ‘Hi!’;
}
third();
console.log(welcome2 + name);
}
function third() {
var welcome3 = ‘Hey!’;
console.log(welcome3 + name);
}
first();
The code executes in serial order according to the numbers. The diagram on the right is essentially the execution stack. If you are not familiar with the stack data structure, a stack is a structure where a first-in-last-out system is maintained. This means that there is only one way for data to exit, and when data is taken out, it must be from the top. If you stack many glass plates one on top of another, you have to take the top plate first; otherwise, you risk an accident. In a stack data structure, the last plate you place is the one you must take first since it’s at the top and accessible to you. The first plate you placed will be at the bottom, and you can take it only after all the plates above it are taken.
When our code is at number 1, the variable name is assigned the value ‘Zonayed’. Then the function definitions for first(), second(), and third() are added to the execution stack in the global execution context. Nothing inside the functions is executed yet since none of them have been called.
At number 5, we call the first() function. Now, the first() execution context is placed on top of the global execution context, and the code inside first() starts executing. At number 6, a variable is assigned some data. At number 7, another function second() is called. So, the second() execution context is placed on top of the first() context, and the code inside second() starts executing. At number 8, a new variable is assigned some data. At number 9, another function third() is called. So, the third() execution context is placed on top of the second() context, and the code inside third() starts executing. Inside third(), at number 10, another variable is assigned some data, and the console log displays it. Therefore, this log will be the first output you see. Once third() finishes executing, it is removed from the stack, and the execution context goes back to second(). Now, at number 12 in second(), the console log prints to the output, which will be the second output you see. Similarly, second() finishes and is removed from the stack. The first() execution context is now at the top, so it executes. At number 13 in first(), the console log prints to the output, which will be the last output you see. This is how the execution stack in JavaScript works, and that’s why we see such outputs.
Post Comment