Professional Development · Technology

Getting Your Head Around JavaScript Closures

One concept that is considered “advanced” in JavaScript is closures. MDN defines closures as:

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

In learning closures, a beginner may be exposed to many contrived examples, adding to the confusion around what closures are and why they are useful. The following is the simplest, clearest example I have seen:

The requirement is to have button that increments a counter. This can be done very easily as follows:

<button onclick="updateClickCount()">click me</button>  
<hr>
<div >Count is: <span id="myCount">0</span></div>

<script>
  var counter = 0;
  var countSpan = document.querySelector('#myCount');

  function updateClickCount() {
      ++counter;
      countSpan.innerHTML = counter;
  }
</script>

Working example is here: https://jsfiddle.net/L3fabh2z/

The problem with this approach is the variable counter is being initialized as a global variable, which means it can be (accidentally or nefariously) changed by any script running on the page. What we want to do is limit the scope of the variable, i.e. to take it out of the global scope, and put it in the function scope, like this:

<button onclick="updateClickCount()">click me</button>  
<hr>
<div >Count is: <span id="myCount">0</span></div>

<script>
  var countSpan = document.querySelector('#myCount');

  function updateClickCount() {
      var counter = 0;
      ++counter;
      countSpan.innerHTML = counter;
  }
</script>

Now the problem is the variable counter gets reset to 0 every time the function is called, so we can never increment past 1.

Working example is here: https://jsfiddle.net/fd60rtoc/

We solve this issue and allow the variable to remain in the function’s scope by using a closure. The below closure is an immediately invoked function expression (IIFE) that initializes the counter variable and returns the function to update the value. The counter variable is initialized when the script runs. Because the function is assigned to the variable updateClickCount and run with (), the counter variable is available to the function scope in perpetuity. It looks like this (note I’ve also changed var to let to align with modern ES6+ syntax):

<button onclick="updateClickCount()">click me</button>
<hr>
<div>Count is: <span id="myCount">0</span></div>

<script>
  let countSpan = document.querySelector('#myCount');

  let updateClickCount = (function() {
    let counter = 0;

    return function() {
      ++counter;
      countSpan.innerHTML = counter;
    }
  })();

</script>

Working example is here: https://jsfiddle.net/4pf57to6/

Note that closures may be created by using an IIFE or by running assigning the function and running it to another variable later, for example in this contrived example:

<button onclick="updateClickCount()">click me</button>
<hr>
<div>Sum of 1 plus 5 is: <span id="myCount">?</span></div>

<script>
  let countSpan = document.querySelector('#myCount');

  const createAdder = (addAmount) => {
    return (val) => val + addAmount;
  }
  const closureAddOne = createAdder(1);

  function updateClickCount() {
    countSpan.innerHTML = closureAddOne(5);
  }

</script>

Working example here: https://jsfiddle.net/osvc1he9/

You’ll see there’s no IIFE in this example, and the closure is initialized when it is called to create the function closureAddOne.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s