The art of programming is the skill of controlling complexity.
It’s pretty interesting!
In this blog, I’ll walk you through your code’s journey, illustrating the inner workings of advanced concepts like scope chain, hoisting, asynchronous, and function execution concerning execution context and the call stack.
All subtopics are related, so please don’t skip them — grab a cup of coffee, and enjoy reading!.
The first thing that happens inside the engine…
Your code isn’t magic. Someone else wrote a program to translate it for the computer.
When you write a program, a syntax parser reads your code and then the compiler translates it into computer instructions(a lower-level language).
Take a look at this simple code:
Where you write your code and what surrounds it is crucial! The area of the code you are looking at physically is the lexical environment.
In the above example, I would say the variable “greetings” sits lexically inside the function sayHello.
It sounds funny, but not every programming language is like that. In JS, however, the compiler that converts your code cares about where you put things. And based on it, decide where your code will sit in the memory and interact.
The Global Execution Context
When the compiler first looks at your code, it asks the JS engine to create a global execution context for you.
The following duties are carried out:
• Create a Memory Heap to keep all variables and functions at a global level
• Create a Global Object and “this” keyword
The location of “this” depends on where your code is running. In the browser, for example, it points to windows objects. Nodejs, for example, will point to a different global object.
Before proceeding with the code execution, it is vital to grasp the hoisting. And for that, we need to look at the memory allocation.
Execution Context: Hoisting
We are frequently baffled when it comes to hoisting. One of the most fundamental JS concepts to grasp and among the interviewer’s top questions.
Take a look at the code above. We ran our code before the declaration and, sayHello() returned “Hello!” whereas myVar logged undefined?
Why is that?
We need to look at the execution context creation phase. When your parser runs through your code, it recognizes where you have created the variables and functions, and so it sets up the memory space for them — Which explains why you can access functions and variables before they are declared.
Now that your code has been compiled and memory space has been set up, it’s time to execute it.
Execution context — function invocation and the Call Stack
When your code is executed line by line…
As your code is executed, the variable favorite is assigned to “C#”, and when it encounters favLanguage(), it says, “Ahh, I need to invoke this function.”
It creates a new execution context and puts it on top of the call stack — like an actual stack in data structures that follows the Last In First Out (LIFO) principle. It has its creation and execution phases, where memory space for any variables or function calls inside side favLanguage() is assigned.
Here’s how the above explanation looks in real-time!
Even though "favorite" is declared 3 times, they are unique and don't touch each other as they exist in their execution context within its variable environment.
Quick note: When a function completes its execution, JS immediately freezes and garbage-collects the memory associated with it to prevent memory leaks, unless you’re using closures, in which case it will only release the memory once all of its dependant functions have been removed from the call stack.
Execution Context: Scope Chain
How does a function access variables outside of its execution context?
What do you anticipate console.log (sport) to return in the code below?
I hope you said “basketball”. As we already know when otherSport() ran, an execution context was generated, triggering favoriteSport(), which likewise produced another execution context. When console.log (sport) was run, it attempted to locate the variable declaration in the favoriteSport function.
Still, when it couldn’t, it examined the outside reference and went hunting for the variables someplace down the call stack. And the external reference point is determined by where your function is lexically situated.
For example, favoriteSport() is lexically positioned not within function otherSport but at the global level. As a result, its external environment is global, and it discovered “sport” in the global execution context, hence logging “basketball”
Here is a bit of fun exercise for you guys. What do you think console.log will return and why? Explain the code execution or draw a scope chain like the above. Let me know in the comments.
JS fascinatingly handles asynchronous tasks!
The web browser comes with a web API that handles HTTP requests, managing dom events, or delaying execution like setTimeout. We call these web APIs asynchronous.
The call stack will be informed that I have something for you through the Event Loop. A task from the callback queue is added to the call stack and logged as "Hi, I'm awake" when the call stack is empty.