Understanding Closures

Understanding Closures

·

12 min read

Introduction

Closures is probably one of the most important concepts in JavaScript that's very popular in interviews.

My friend Simon Hoiberg has a great video explaining this within 2 minutes.

In this article, I have explained concepts from absolute fundamentals starting with JavaScript's execution context to the scope and finally Closures.

I can assure you that after going through this article, you'll be able to understand and explain Closures.

The following topics are covered in this article:

  • What is an execution context?
  • What are the creation & execution phases in an execution context?
  • What are the Global & Function execution contexts?
  • What is an execution stack & scope?
  • And Finally - What are Closures?

Execution Context

JavaScript engine to break up parts of the code to manage the complexity of interpreting and running it. If you have to explain this easier, think about a scenario where you attempt to write a web application. Your application typically would comprise of modules, functions, variable declarations, etc. Essentially, you have broken down the application into logical pieces in order to understand, maintain, and debug it.

Just like how modules, functions, etc. allow you to manage program complexity, Execution Context is the JavaScript engine's way to manage the complexity of interpreting the code. Hope makes things a little clearer.

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'.

Execution_Context.png

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.

creation_phase.png

In the Global Creation phase, the JavaScript engine will:

  1. Create a global object.
  2. Create an object called “this”.
  3. Set up memory space for variables and functions.
  4. 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.

execution_phase.png

In the Global Execution Phase, the JavaScript engine will:

  1. Starts running the code line by line.
  2. 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.

FunctionExecutionContext-Creation.png

In the Function Execution Context -Creation phase, the JavaScript engine will:

  1. Create an arguments object.

  2. Create an object called 'this'.

  3. Set up memory space for variables and functions.

  4. Assign variable declarations a default value of “undefined” while placing any function declarations in memory.

  5. 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.

FunctionExecutionContext-Execution.png

In the Function Execution Context - Execution Phase, the JavaScript engine will:

  1. Starts running the code line by line inside the function.
  2. Assigns the 'real' values to the variables already present in the memory.

This is similar to the 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 is popped off the Execution Stack.

This is the foundation of the concept 'Scope' in JavaScript. We will discuss that further, but first, look at what an Execution Stack is in JavaScript.

Execution Stack

JavaScript engine creates the Execution Stack (also known as the 'Call Stack') and 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 an 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, the creation & execution phases of the execution context are identical. Hence, I have consolidated the same in the illustration below. The thing to note is that the stages happen sequentially, first the creation, followed by the execution phase.

The main intent of the example was to demonstrate the nested structure of how the function execution contexts are contained within each other in a parent-child relationship.

NestedExecContext-Creation.png

With this, we have a very good foundation to discuss the next important concept, Scope.

Scope

The 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 outside 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 the 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 concepts such as Execution Context, Stack, Scopes, etc. we are almost ready to describe Closure.

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 the 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.

Closures.png

Important things to note:

  1. In the diagram above, if you observe the right hand column, the last row contains something called 'Closure Scope'.
  2. 'Closure Scope' is a process in which the JavaScript engine creates a scope storing the variables of the parent function. This is accessible even when the parent function's execution context is popped off the call stack.
  3. 'Closure Scope' creation is done by the JavaScript engine only for nested functions.
  4. In the above example, the inner function getDestination() creates a closure over the outer function getVacation() function variable environment.
  5. 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.

I have tried my best to simplify and explain the above concepts. I hope it made sense.

So, in conclusion, 'Closures' is a concept of a child function "closing' over the variable environment of its parent function.

Conclusion

To summarize, we have covered the following concepts in this article.

  • Execution Context - Global & Function Execution Context
  • Phases of Execution Context - Creation & Execution
  • Execution Stack
  • Scope/Scope Chain
  • Closures

I realized this has been quite a long article. But, I hope I could touch upon the above topics and explain closure in a way you can relate.

Don't forget to subscribe to my newsletter. You can connect with me on Twitter @skaytech

You may also be interested in the following:

Did you find this article valuable?

Support Skay by becoming a sponsor. Any amount is appreciated!