Introduction
If you have read my article on 'Understanding Hoisting', you can skip the initial sections and proceed to the section 'Function Execution Context'. I have duplicated the first few sections in this article, to ensure there's continuity in explanation.
In this article, we will cover the following topics:
- What is an execution context?
- What are creation & execution phases in an execution context?
- What are Global & Function execution context?
- What is an Execution or a Call Stack?
- What is Scope & Scope Chain?
- And Finally - What are Closures?
Execution Context
JavaScript engine break up parts of the code to manage the complexity of interpreting and running it. Think about a scenario where you attempt to write a web application. You will break down the application into modules, functions, variable declarations, etc. You do this in order to preserve the maintainability & readability of code.
Just like how modules, functions, etc. allow you to manage the program's complexity, Execution Context is the JavaScript engine's way to manage the complexity of interpreting the code. Hope the analogy makes it easier to understand.
Global Execution Context
The first execution context that gets created when the JavaScript engine runs your code is called the “Global Execution Context”. Initially, this Execution Context will consist of two things - a global object and a variable called 'this'.
The above image represents the global execution in the most basic form. The 'this' keyword references the global object which is the 'window' object.
Creation & Execution Phases
Now that we understand the global execution context, let us understand the two phases that exist while running any JavaScript program.
Let us consider the following code example:
var fruit = apple;
function getFruit() {
return fruit;
}
Creation Phase
The below diagram depicts how the Global Execution Context looks during the creation phase.
In the Global Creation
phase, the JavaScript engine will:
- Create a global object.
- Create an object called “this”.
- Set up memory space for variables and functions.
- Assign variable declarations a default value of “undefined” while placing any function declarations in memory.
Execution Phase
The below diagram depicts how the Global Execution Context looks during the execution phase.
In the Global Execution
Phase, the JavaScript engine will:
- Starts running the code line by line.
- Assigns the 'real' values to the variables already present in the memory.
Now that we've understood the creation & execution phases, let us take another example and look at the output on the console.
console.log(`The fruit is ${fruit}`);
console.log(`The color is ${color}`);
var fruit = 'apple';
var color = 'red';
function getFruit() {
return fruit;
}
function getColor() {
return color;
}
//Output
//The fruit is undefined
//The color is undefined
Things to note:
- During the creation phase, the variables 'fruit' & 'color' are initialised with the values 'undefined'.
- Hence when the console.log statement is encountered, the value 'undefined' is printed on the console.
Function Execution Context
The Function execution context is created whenever a function is invoked. It is very similar to the Global execution context and the important thing to remember is that it will be created when the JavaScript engine starts interpreting your code.
Let us look at the below code sample:
/*
The Variables fruit1 & fruit2 are created in the Global Execution Context
*/
var fruit1 = 'apple';
var fruit2 = 'banana';
/*
The Variable color is created within the Function Execution Context
*/
function getFruitColor(fruit) {
var color;
if(fruit === 'apple') {
color = 'red';
} else {
color = 'yellow';
}
return color;
}
//Only at this point when the function is invoked,
//the function execution context is created
getFruitColor(fruit1);
As we saw from the previous example, we have two phases, the 'Creation' & 'Execution' phases of the Function Execution Context.
An important thing to remember is that, the Global Execution Context is created only once at the beginning whereas the Function Execution Context is created when the function is invoked for execution.
Creation Phase
The below diagram depicts how Function Execution Context looks during the creation phase.
In the Function Execution Context's Creation phase, the JavaScript engine will:
Create an arguments object.
Create an object called 'this'.
Set up memory space for variables and functions.
Assign variable declarations a default value of “undefined” while placing any function declarations in memory.
An 'argument's object is created and will it will be added as a local variable to the Function Execution Context.
Just as we had discussed above, when the function 'getFruitColor' is invoked, the new Execution Context is created.
A significant difference compared to the Global Execution Context is the 'argument' passed into the function. Any argument passed to the function will be added as a local variable to the Function Execution Context.
Any variables declared within the function will live inside the Function's Execution Context.
Execution Phase
The below diagram depicts how Function Execution Context looks during the execution phase.
In the Function Execution Context's Execution Phase, the JavaScript engine will:
- Starts running the code line by line inside the function.
- Assigns the 'real' values to the variables already present in the memory.
This is similar to Global Execution Context, except for the fact that as soon as the function execution is completed, the variables are removed from the Stack.
Let us look at the code example below.
/*
The Variables fruit1 & fruit2 are created in the Global Execution Context
*/
var fruit1 = 'apple';
var fruit2 = 'banana';
/*
The Variable color is created within the Function Execution Context
*/
function getFruitColor(fruit) {
var color;
if(fruit === 'apple') {
color = 'red';
} else {
color = 'yellow';
}
return color;
}
//Invoking the Function getFruitColor() creates the Function Execution Context
getFruitColor(fruit1);
//The Variable Color inside the function getFruitColor() is not accessible
//Output -> "Uncaught ReferenceError: color is not defined"
console.log(`Value of color inside the function is ${color}`);
Things to note:
- The variable 'color' is not accessible from outside of the function, since as soon as the function 'getFruitColor' completes execution, the variable 'color' is popped off the Execution Stack.
This lays the foundation to discuss the concept 'Scope' in JavaScript. We will discuss that a little further, but first let us look at what an Execution Stack is in JavaScript.
Execution Stack
JavaScript engine creates the Execution Stack (also known as the 'Call Stack') while executing the code. Anytime a function is invoked, a new Execution Context is created and added to the Execution Stack. When the function completes through the 'creation' and 'execution' phases, it will be popped off the execution stack.
The code below will help us visualize how the Execution Context is created and added to a Execution Stack in a nested manner.
function house() {
console.log('Inside House');
function room() {
console.log('Inside Room');
function closet() {
console.log('Inside Closet');
}
// No.3 - From the 'room' Function Execution Context - the function closet() is invoked
closet();
}
// No.2 - From the 'house' Function Execution Context - the function room() is invoked
room();
}
// No.1 - From the Global Execution Context - the function house() is invoked
house();
/*
Output
Inside House
Inside Room
Inside Closet
*/
Creation/Execution Phases
In the above example, since there are no variables involved, the creation & execution phases of the execution context are identical.
Hence, I have consolidated them in a single diagram as shown below.
Also, another important thing to observe is the the nested structure of how the function execution contexts are contained within each other in a parent-child relationship.
With all of the above concepts covered, we can move to the next topic Scope.
Scope
Scope is generally referred to as the logical boundary within which your variables are accessible. Any variable that is defined outside of a function is by default added to the Global scope. They get added to the 'window' object of the global scope as shown in the example below.
//Variable added outide of a function, by default, gets added to the global object - window
var phone = 'iPhone';
//When 'this' keyword is output on the console, the whole window objects gets printed on teh console.
console.log(this); // Output -> window object
//Since the variable is added to the global 'window' object, this.phone will display 'iPhone' on the console
console.log(this.phone); //Output -> iPhone
Now, let us look at an example of what happens when you try to access a variable created a function, outside of the function.
//A simple function that initializes and assigns the variable mojito
function drink() {
var mojito = 'An Alcoholic beverage comprising of Rum, lime, mint & sugar.';
}
//Invoke the function 'Drink'
drink();
//Output -> ReferenceError: mojito is not defined
console.log(mojito);
When the function execution context of 'drink' is completed, the variable 'mojito' is popped off the execution stack. Hence it is no longer accessible in the global execution context and hence the 'ReferenceError' gets output on the console.
This means that the variables created inside the function are accessible only within the function or in other words they are locally scoped.
Hence, Scope can be defined simply as the imaginary/logical boundary within which the defined variables and functions are accessible.
Closures
Now that we have touched upon the various concepts such as Execution Context, Stack, Scopes, etc. we are almost ready to talk about Closure. But, first, Scope Chain.
Scope Chain
Let us look at the code example below:
//Initialize animal variable with 'dog'
var animal = 'dog';
//Display the value of variable animal on the console
function displayAnimal() {
console.log(animal);
}
displayAnimal();
//Output -> dog
While you might have thought that the output should have been 'undefined' or variable not defined inside the function displayAnimal(), since there is no 'animal' variable inside the Function Execution Context, or in other words, it isn't present in the local scope.
However, the output displayed on the console is 'dog', since the function is able to access the variable 'animal' which is present in the Global Execution Context.
This is possible through the concept called 'Scope Chaining'. The JavaScript engine tries to look for the variable defined within the Function Execution Context, if it is not possible, then it looks to the nearest parent execution context for the variable and in the above example, the immediate parent execution context was the 'Global Execution Context'.
Nested Functions
Functions that are present inside another function are referred to as Nested Functions. So far, we've discussed that the variables within a function cannot be accessed outside it, since they get popped off the execution stack. While it is true for most part, with nested functions, the behavior is slightly different.
Let us look at the below code example.
//Outer Function - getVacation() returns the inner function myDestination()
function getVacation(){
//The variable myDestination is defined within the outer function - getVacation()
var myDestination = "Venice";
//Inner Function - getDestination returns the variable "myDestination" defined in the outer function
function getDestination(){
//Return the variable on function invocation
return myDestination;
}
//Return the inner function - getDestination when the outer function getVacation() is invoked
return getDestination;
}
//When the outer function getVacation() is invoked, it returns a reference of the inner function - getDestination()
var myVacation = getVacation();
//When the variable myVacation is executed - it executes the inner function 'getDestination' since it holds the reference
console.log(`My favorite destination is ${myVacation()}`);
//Output -> My favorite destination is Venice
Things to note:
- The function getVacation() (outer function) returns the getDestination() (inner function).
- This is assigned to the variable 'myVacation'. In other words, the variable 'myVacation' holds a reference to the inner function getDestination().
Now, let's look at how the Execution Context (Creation/Execution) phases for both the inner and the outer functions.
Important things to note:
- In the diagram above, if you observe the right hand column, the last row contains something called 'Closure Scope'.
- 'Closure Scope' is a process in which the JavaScript engine creates a scope environment storing the variables of the parent function.
- The important thing to note is that this closure scope is accessible even when the parent function's execution context is popped off the call stack.
- 'Closure Scope' creation is done by the JavaScript engine only for nested functions.
- In the above example, the inner function getDestination() creates a closure over the outer function getVacation() function variable environment.
- Through the Closure Scope (via scope chain), the getDestination() function can access the 'myDestination' variable (Venice) even after the outer function getVacation()'s execution context is popped off the execution stack.
So, in conclusion, Closures is a concept of a child function "closing' over the variable environment of it's parent function.
I have tried my best to simplify and explain the above concepts. I hope you were able to follow the same.
Conclusion
To summarize, we have gone through the following concepts in detail in this article.
- Execution Context - Global & Function Execution Context
- Phases of Execution Context - Creation & Execution
- Execution Stack
- Scope/Scope Chain
- Closures
Do let me know your feedback & comments and do not forget to share it with your friends.
You may also be interested in:
Top comments (1)
Question about popping off from stack.
So from your post I understood it as parent function gets popped off from the call stack even before it's child starts its Execution Context ?.
I thought parent should be popped whenever child completed its flow then child is cleared from stack then followed by Parent. Because in this way it makes some sense how it should be working.
But from what I red from your point of view is parent is popped well before the child starts even though Closure is holding the parent data .
Aside all of this , It's good post I red most of your posts one after another this were excellent