Closure in JavaScript Explained

Closure in JavaScript has developed a bit of a reputation over the years. Although it can be a bit tough to wrap your brain around initially, once you see in in action you’ll be surprised at how simple it really can be.

A Quick Refresher on Variable Scope

The first thing you should be familiar with is the idea of variable scope. As you may know, in JavaScript variables defined with var are said to be scoped to the function they are defined in, or globally if they don’t exist in any specific function. This means that anything defined inside a function only exists inside that particular function. Let’s take a quick look at this in action.

var a = "outside the function";

function functionOne() {
    var a = "inside the function";
    console.log("logged from inside the function: " + a);
}

Here we have two different variables named a. One is defined in the global frame outside of any functions, and one inside the functionOne function. If we were to simply log the value of a with console.log() It would display one value:

console.log("logged from outside the function: " + a);
"logged from outside the function: outside the function"

But if we call functionOne it will log a different value:

functionOne();
"logged from inside the function: inside the function"

These different values for a are a result of the functional scope for variables defined with var. As we’ll soon see, closure deals directly with the idea of variable scope, so it’s important to have a good understanding of the concept.

Nested Functions

A common practice in JavaScript is to nest one function inside of another. Functional scope for var variables still applies in this situation, but something that might not be apparent is that nested functions have access to the variables of the ‘parent’ functions that contain it.

Let’s take a look at an example to see what’s going on:

function funcOne() {
  var myString = "This string is defined inside funcOne";
  function writeString() {
    console.log(myString)
  }
  writeString();
}
funcOne();

Output:

This string is defined inside funcOne

Here’s a play-by-play of what happens when funcOne is called:

  1. funcOne defines a variable, myString, and a function, writeString.
  2. funcOne then calls the writeString function.
  3. writeString simply logs the variable myString.

Note that writeString has access to myString automatically thanks to JS’s lexical scoping. WriteString doesn’t define the variable nor is it passed in as a parameter.

Your First Closure

So far so good, hopefully. Let’s change the code block above slightly to make use of closure:

function funcOne() {
  var myString = "This string is defined inside funcOne";
  function writeString() {
    console.log(myString)
  }
  return writeString;
}
var myFunc = funcOne();
myFunc();

Output:

This string is defined inside funcOne

Here’s another play-by-play of what’s going on this time:

  1. The funcOne function defines the myString variable as a string.
  2. The writeString function is defined inside of funcOne. All it does is log myString.
  3. The funcOne function then returns writeString, without actually calling it.
  4. The myFunc variable is defined as the output of the funcOne function.
  5. The myFunc function is now called, which calls the writeString function that was defined inside funcOne.
  6. The writeString function logs myString.

Okay, while obviously there’s a lot more going on here, from a compositional standpoint, it’s very similar code to the previous example. The key difference is that now instead of funcOne defining and then running writeString, it now defines it and returns the function itself.

That means when we define myFunc as the result of funcOne(), we’re essentially defining myFunc as the writeString nested function. Just as in the first example writeString has access to the myString variable, but so does myFunc.

The key thing to understand here is that when a function returns another function that is nested inside of it, you’re not only returning the nested function, you’re returning the context in which the nested function was defined. This is what’s know as a closure. This is possible in JS because functions are first-class objects.

Closure Defined

Here’s a more formal definition that you may find helpful:

A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time that the closure was created.

MDN: Closure

One of the neat things that closures makes possible is functions that create other functions, but with the same initial conditions for every new instance.

var makeAdder = function(a) {
  var adder = function(b) {
  console.log(a + b)
  }
  return adder 
}

var add5 = makeAdder(5);

With add5 we’ve created a version of the adder function with the a variable permanently set to 5. You can now pass any number to add5 and it will add 5 to it.

add5(2);
> 7
add5(7);
> 12

Thanks to closure we can use the makeAdder function to make a completely different adder:

var add10 = makeAdder(10);

With add10 the a variable has been set to 10 and you can pass any number to add10 and 10 will be added to it.

add10(2);
> 12
add10(7);
> 17

Now that’s neat and all, but maybe not the most practical example. Let’s take a look at some instances where closures can be quite helpful in your production code.

Private Variables

Closures allow you to mimic some of the features of object-oriented languages that JavaScript lacks on its own such as private values.

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  };
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      console.log(privateCounter);  
    }
  }  
};

This is very similar to the other closures we saw above. makeCounter defines an initial variable, privateCounter, and a function, changeBy, which has access to privateCounter. Instead of returning a single function as we saw earlier, it actually returns an object which contains 3 functions. Two of these functions call the ChangeBy function, and as a result have access to privateCounter through the magic of closure.

We can now create a new counter with this code and use any of these methods to count up or down.

var counter1 = makeCounter();
counter1.increment();
counter1.value();
> "1"
counter1.increment();
counter1.value();
> "2"
counter1.decrement();
counter1.value();
> "2"
console.log(counter1.privateCounter);
> undefined

Notice that when we try to access the privateCounter variable directly undefined is returned. That’s because the variable is out of scope in that context, but the increment, decrement and value functions still have access thanks to closure.

We could then create another separate counter if we wanted to, and it would not be affected be the first one, even though they both start with the same initial value.

var counter2 = makeCounter();
counter2.decrement();
counter2.value();
> "-1"
counter2.decrement();
counter2.value();
> "-2"
counter2.decrement();
counter2.value();
> "-3"

Using closure to create little tiny factories in this way is sometimes known the module pattern.

In Conclusion

One of the reasons closure and JavaScript are so closely associated with each other is that it’s not always possible in many other languages:

In JavaScript, if you use the function keyword inside another function, you are creating a closure. In C and most other common languages, after a function returns, all the local variables are no longer accessible because the stack frame is destroyed. In JavaScript, if you declare a function within another function, then the local variables can remain accessible after returning from the function you called.

StackOverflow

So there you have it, as you can see, it’s really nothing too be intimidated by, even if you do want to do things a little more exciting than adding numbers together. We’re really just scratching the service with the ways that closure can be utilized in JavaScript programming, but this should give you a solid understanding to build on.