DEV Community

Cover image for AN EASY GUIDE TO UNDERSTANDING CLOSURES IN JAVASCRIPT
Lawrence Eagles
Lawrence Eagles

Posted on • Updated on

AN EASY GUIDE TO UNDERSTANDING CLOSURES IN JAVASCRIPT

Table of Contents

  1. An Introduction To Closure
  2. The Fundamental Tenets
  3. Scope And Scope Chain
  4. A Second Look At Closure
  5. Closing Thoughts

1. An Introduction To Closure

Closures are an extremely powerful feature of the JavaScript programming language.

💡 A closure is a combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time. MDN

The superb definition above utterly explains a closure. It is actually a feature of the JavaScript language, it is not something we code; it just happens due to the way the JavaScript language works. As a result of this, a function is able to access the variables of its parent (outer) function even after that function has returned.

Let's throw more light on the above definitions with an example below:
Kindly run the code below and consider its result.

function getBio(shortDevBio) { return function(devName) { console.log("Hi, my name is " + devName + " " + shortDevBio); } } const talkAboutDev = getBio("I am a developer, writer and instructor") talkAboutDev("Lawrence Eagles")

Our small contrived example above features a function getBio that takes a succinct developer bio and returns another function (an anonymous function) that takes a developer name. This inner function then tells us about the developer by logging his name and his bio to the console.
One thing to note is that the getBio function does not return a function call but rather it returns an anonymous function. This means that when the getBio function is called it returns the code below:

function(name) {
        console.log("Hi, my name is " + name + " " + shortDevBio);
   }
Enter fullscreen mode Exit fullscreen mode

And because this is an anonymous function we assigned it to the variable talkAboutDev. Then we called this anonymous function through the talkAboutDev variable which now holds a reference to it in memory.
I have already explained why this is possible in my previous article in the functional programming in JavaScript series.
If this is not very clear for you, I would kindly suggest that you refer to my article on anonymous and first-class functions in JavaScript for a quick brush-up. You can access it below:

So when we call the talKAboutDev function, it tells us about the developer whose bio was passed to the getBio function.
This is puzzling❗

How did the talkAboutDev function get the bio of the developer since the getBio function has already returned before it was called ❓

You can have a second look at the codes as you digest this question:

function getBio(shortDevBio) {
    return function(devName) {
        console.log("Hi, my name is " + devName + " " + shortDevBio);
   }
}

const talkAboutDev = getBio("I am a developer, writer and instructor")
talkAboutDev("Lawrence Eagles") // returns "Hi, my name is Lawrence Eagles I am a developer, writer, and instructor"

// But how did it get the developer bio?
Enter fullscreen mode Exit fullscreen mode

💡 This is possible because of closure. The inner function talkAboutDev still has access to the (free) variables of the outer (getBio) function even after it has returned.

The above answer may not really be satisfying especially if you do not have good knowledge of closure in JavaScript before now. We will be taking a deep look at this notorious, often difficult to understand, and extremely powerful feature in the JavaScript programming language in the remaining sections below.

It is very important to have a good understanding of closure in JavaScript especially if you want to level-up your skill of the language.

In other to fully understand closure in JavaScript, we need a solid understanding of some key concepts, which are its fundamental tenets.
We will be looking at these in the next section.

2. The Fundamental Tenets

Section 1 gives us an overview of closure. Although we saw it in action there are still some unanswered questions. In other, for us to get a thorough understanding of it, we need to learn about the key concepts that come into play when closures are created in JavaScript.
Let's deal with them below.

1. The Execution Context.

💡 Every code in JavaScript runs inside a wrapper around it called the execution context

When a JavaScript program runs, a base (global) execution context is created and it wraps around all the codes.
Consider the image below:

Alt Execution context

From our image, we can see that the global execution context is made up of the global object, the this variable, the variable environment, and the outer environment.

In other to get a good understanding of closure and even the JavaScript language we need to learn about all these and how they interact when our program runs.

The Global Object

This is the window object. It represents your browser's current tab. If you open another tab, you would get a separate global object because that would create a separate execution context. In a Node.js environment, however, the global object is not the window object.

💡 In the browser the global object is the window object but in Node.js the global object is called the global object

Kindly run and consider the result of the code below:

console.log(this)

💡 When the JavaScript engine runs your code for the first time, the global execution context is created. This would still be created even if the .js file is empty

The runkit program above is an empty .js file. Notice that the global execution context was still created hence we get the global object in the console. Note runkit is a node.js environment so the global object is called global
Alt the global object in console

The this variable or keyword

This is a special JavaScript object. I have dealt with it in more detail in one of my articles in the OOP (Object Oriented Programming) in JavaScript series. kindly read more about it below.


All we would say here is that at the global level the this variable is equal to the global object. It points to it.
The Variable Environment

This refers to where the variable lives in memory and how they relate to each other. Each execution context has its own variable environment. For the global execution context, the variable environment is the global object.

The outer Environment

When we execute code inside a function, the outer environment is the code outside that function but at the global level, the outer environment is null because there is nothing outside it. We are at the outermost level.

Let's elaborate on these by considering some examples.

Kindly examine the code below.
In what order do you expect to see the three
console.log() results❓

💡 A good tip to note as you go through this exercise, is that a new execution context is created whenever a function is called, and this is added to the top of the execution stack. Also whenever a function returns its execution context is removed from the execution stack

function father() {
    child();
    let lastName = "Eagles"
    console.log(lastName)
}

function child() {
   let firstname = "Lawrence";
   console.log(firstname)
}

father();
var fullName = "Lawrence Eagles";
console.log(fullName);
Enter fullscreen mode Exit fullscreen mode

Before we run the example above on runkit, let's take a deeper look at how the JavaScript engine would execute this code.

  • At first the global execution context is created and all these functions and variables are added to a place in memory (in the global execution context, this is the global variable).

There are two phases in the creation of the execution context viz the creation phase and the execution phase. I have covered this in an old article you can access it below.

  • During the execution phase of the global execution context creation, the father() function is called and this creates a new execution context that is placed on top of the execution stack. The codes within this execution context (literally the codes in this function's code-block) will then be executed.

  • The child() is called as the codes within the father function's code block are executed and a new execution context is created and placed on top of the execution stack.
    The codes within the child function's execution context (the execution context on top of the execution stack) will now be executed.

💡 As a rule if a function is called within a function a new (the inner function's) execution context is created and put on top of the execution stack and until the inner function returns and its execution context is removed from the execution stack the codes of the parent function will not be executed.

  • During the execution of the codes in the child function's execution context, the string "Lawrence" is assigned to the firstName variable and that is logged to the console.

  • The child function returns and its execution context is popped off the execution stack (it is removed). The parent function's execution context now sits on top of the execution stack; thus the execution of its code will now continue.

  • Next, the string "Eagles" is assigned to the variable lastName and that is logged to the console. This marks the end of the execution of the parent function; consequently, its execution context is popped off the execution stack and we have the global execution context left.

  • Only now will the remaining codes in the global execution context be executed. The string "Lawrence Eagles" is now assigned to the variable fullName and that would be logged to the console.

From the explanation above we expect to get this result:

// "Lawrence"
// "Eagles"
// "Lawrence Eagles"
Enter fullscreen mode Exit fullscreen mode

Kindly run and examine the code below.

function father() { child(); let lastName = "Eagles" console.log(lastName) } function child() { let firstname = "Lawrence"; console.log(firstname) } father(); var fullName = "Lawrence Eagles"; console.log(fullName);

3. Scope And Scope Chain

As we look at the scope and scope chain in this section, we will elaborate on the variable environment and the outer environment with code examples

Kindly consider the codes below.

💡 A good tip to note is that when the JavaScript engine does not see a variable in the variable environment of an execution context, it would go out to its outer environment to look for it.

function logDevName() {
   console.log(devName)
}

function logDevName2() {
    var devName = "Lawrence Eagles"
    console.log(devName)
    logDevName()
}

var devName = "Brendan Eich"
console.log(devName)
logDevName2()
Enter fullscreen mode Exit fullscreen mode

What do you think would be the values of the devName variable at each console.log()

To answer this question let's go through the way the JavaScript engine would execute this code.

  • First the global execution is created and all these functions and variables are added to a place in memory (in the global execution context, this is the global variable).

  • During the execution phase of the global execution context creation, the string "Brendan Eich" is assigned to the variable devName and that is logged to the console.

  • Then the logDevName2 function is called and a new execution context is created and put on top of the execution stack.

  • In the execution of the logDevName2 function, the string "Lawrence Eagles" is assigned to the variable devName and that is logged to the console; thus, devName in this execution context is "Lawrence Eagles".

  • Next, the logDevName function is called and a new execution context is created and put on top of the execution stack.

  • During the execution of this function, the variable devName is logged to the console. But it is not in this local Scope because it is not in the variable environment of this functions' execution context (it is not declared within this function).

💡 A scope is a place where a variable can be found.

  • So the JavaScript engine would go out to its outer environment to look for this variable; in this case, the outer environment is the global execution context. This is so because of the lexical environment of the logDevName function.

💡 The lexical environment refers to where something is written physically in your code. The JavaScript engine uses this to determine how things would sit in memory and how they would connect to each other. In our example, both the logDevName and the logDevName2 functions are sitting in the global execution context; therefore, it is the outer environment of both functions, even though the logDevName function is called inside the logDevName2 function.

  • The devName variable is found in the variable environment of the global execution context and there it is "Brendan Eich" hence the string "Brendan Eich" is logged to the console. You can have a second look at the code below, and hopefully, you should now have a better understanding as you run it in runkit to see the result.
function logDevName() { console.log(devName) } function logDevName2() { var devName = "Lawrence Eagles" console.log(devName) logDevName() } var devName = "Brendan Eich" console.log(devName) logDevName2()

💡 The JavaScript engine goes out of a function's execution context to its outer environment using the reference to that outer environment and continues to go up until it reaches the global execution context. This connection of links or references to different outer environments is called the scope chain

4. A Second Look At Closure

Since we now have an understanding of all the fundamental tenets required to grasp the concept of closure, let's revisit the first example and answer our long-standing question.

Kindly note this when a function returns and its execution context is removed from the execution stack, its inner function will still have access to the variables in its variable environment as the JavaScript engine performs its search up the scope chain

function getBio(shortDevBio) { return function(devName) { console.log("Hi, my name is " + devName + " " + shortDevBio); } } const talkAboutDev = getBio("I am a developer, writer and instructor") talkAboutDev("Lawrence Eagles")

The inner function is able to get the value of the shortDevBio variable even after the getBio function has returned and its execution context has been removed from the execution stack because its inner function still holds a reference to its variable environment. Thus, the JavaScript engine is able to find the location of the (free) variables (e.g shortDevBio) in the variable environment of the getBio function as it continues its search up the scope chain.

Therefore, we can say that the execution context of the inner function encloses its outer variables. We can also say that it encloses all the variables that it is supposed to have access to. This phenomenon is referred to as closure.

It makes possible some very powerful javascript design patterns used in some of the most popular JavaScript frameworks and libraries.

The code above can be re-written like this using an alternate syntax.
Kindly examine the code below:

function getBio(shortDevBio) {
    return function(devName) {
        console.log("Hi, my name is " + devName + " " + shortDevBio);
   }
}

// uses an alternate syntax to run both functions in one line.
const developerBio = getBio("I am a developer, writer and instructor.")("Lawrence Eagles")
console.log(developerBio)
Enter fullscreen mode Exit fullscreen mode

What do you think the console.log() would output❓

💡 The alternate syntax uses two parentheses. The first () simply calls the outer (getBio) function and since that returns another function the second () calls the inner function, hence we get the same result

You can run the code in runkit below:

function getBio(shortDevBio) { return function(devName) { console.log("Hi, my name is " + devName + " " + shortDevBio); } } // uses an alternate syntax to run both functions in one line. const developerBio = getBio("I am a developer, writer and instructor.")("Lawrence Eagles")

We see this pattern in action when we work with React and Redux using the React-Redux library.

React-Redux is a library that connects React and Redux together. It poses itself as the official React bindings for Redux and it is maintained by the Redux team.

Below is an extract from an example in their official doc.

export default connect(
  null,
  mapDispatchToProps
)(TodoApp)
Enter fullscreen mode Exit fullscreen mode

The details of what is going on here are out of the scope of this article but I just want to point out the way the connect function is called with two parentheses. The first takes null and mapDispatchToProps while the second takes the TodoApp component as its argument the result is then exported.
This pattern is made possible because of closure in JavaScript.

5. Closing Thoughts

It has really been a long article and if you got here you are appreciated.
I do hope that at this point, you can see the benefits of our long discussion and at least got a thing or two from this article. If so I would be looking forward to hearing your opinions, comments, questions, or requests (in case anything is not clear) at the comments section below.

Latest comments (6)

Collapse
 
aissabouguern profile image
Aissa BOUGUERN

Thanks for the awesome article. Long but the subject worh it.
One remark/question: Would not be better if you say call stack rather than execution stack ?

Collapse
 
lawrence_eagles profile image
Lawrence Eagles

Thanks, Aissa for your remark.
Call stack and execution stack actually mean the same thing. They are both correct.

Collapse
 
aissabouguern profile image
Aissa BOUGUERN

Yes I know, but I think Call Stack is used most of the time in JS tutorials and books.
Anyway, thank you again for this tuto.

Collapse
 
mdenoun profile image
mdenoun

Very clear, straight to the point article. Best article on closure I read so far.

Collapse
 
lawrence_eagles profile image
Lawrence Eagles

Hello, sorry this came kind of late.
Both call stack and execution stack are actually two different words for the same thing.

Collapse
 
lawrence_eagles profile image
Lawrence Eagles

Thanks for your comment. Good to hear this.