Tech How To

A Tech Blog for Everyone

Mastering Closures in Javascript

Posted By: Justin Hurley|April 14, 2020

Javascript closures are an important functional programming tool to add to your repertoire. When digging through code written by other developers, you will come across this concept time and again. Learning the concept now will allow you to zoom through foreign code and allow you to familiarize yourself with a code base.

Learning closures has been a large stumbling block for me personally, and the few times that I have come across them in my career, they've thrown me for a loop. I recently committed myself to learning them inside and out, both for personal gain and to use professionally.

Lexical Scoping

Here is a a small example of the usage of a closure:

function counter() {
    var count = 0;
    return function() {
        alert(count++);
    }
}
var add = counter();
add(); // Outputs 0
add(); // Outputs 1
add(); // Outputs 2

This is one of the simplest forms of a closure you'll come across in your travels. This shows one of the two main benefits of closures. The initial storage of the counter function call on line 7 essentially stores a reference to that initial call. The value of the variable count is stored in the Lexical Scope. This means that the values within the returned anonymous function, as well as the values within the scope of the function surrounding it (In our case the countvariable) are passed into the new function variable (add).

This allows you to deal with one instance of the function and keep updating the values inside of it. Remember, that since the function is passed as a variable (functions are a First Class Citizen in Javascript), itself and its values can be modified. Keeping your desired values out of the global scope or an extended block's scope is vital to keeping code mistake free and prevents you from having to chase down bugs that could have been avoided if you just used a closure in the first place

Before we move on to another usage of closures, stop and think of any ways that you could implement this functionality into your own coding workflow.

Use Cases

1.) Using as a Function Factory

An example like this really helped imprint the concept of closures into my head. Its a bit of a contrived frontend example, but perfectly demonstrates the use case for such an architectural pattern. Check out the Codepen to see it in action.

<button id="clickButton1" type="button">Click on Me!</button>
<button id="clickButton2" type="button">Click on Me!</button>
<ul id="listItems">
</ul>

<ul id="listItems2">
</ul>
  
const button1 = document.getElementById('clickButton1');
const button2 = document.getElementById('clickButton2');

// Function to add the items below to their respective lists
const addListItem = (item, list) => {
  var li = document.createElement('li');
  li.textContent = item ;
  list.appendChild(li);
}
  
function addtoListHandler(listId) {
  
  var listItems = [
    'Oranges',
    'Tomatoes'
  ]
  
  var list = document.getElementById(listId)
  
  listItems.map(item => {
    addListItem(item, list)
  })
  
  // This function is returned when 'addtoListHandler' is called
  return function(newItem) {
   listItems.push(newItem)
   addListItem(newItem, list)
  }
}

const list1 = addtoListHandler('listItems')
const list2 = addtoListHandler('listItems2')

button1.addEventListener('click', () => list1('Bananas'))
button2.addEventListener('click', () => list2('Green Peppers'))

When running the above code, two separate instances of the Lexical Scope of the anonymous function on line 25 are created. This means that we can control two separate lists. Note that pressing each of the buttons adds to a separate list and the new values of the list don't get added to both lists?

That is the power of closures. They allow you to maintain the data in each new scope created by the functions without the possibility of accidentally manipulating the values in separate instances.

Without using a closure, like the code below, what would happen then?

const button1 = document.getElementById('clickButton1');
const button2 = document.getElementById('clickButton2');

const addListItem = (item, list) => {
  var li = document.createElement('li');
  li.textContent = item ;
  list.appendChild(li);
}
  
function addtoListHandler(listId, newItem) {
  
  var listItems = [
    'Oranges',
    'Tomatoes'
  ]
  
  var list = document.getElementById(listId)
  
  listItems.map(item => {
    addListItem(item, list)
  })
  
   listItems.push(newItem)
   addListItem(newItem, list)

}


button1.addEventListener('click', () => addtoListHandler('listItems','Bananas')) ;

button2.addEventListener('click', () => addtoListHandler('listItems2','Green Peppers')) ;

If you were to run the code above, you would see that on each click, the list is recreated. Thats not the desired effect here. Since no new reference to the scope of the addtoListHandler function is created, the listItems are re-instantiated after every click.

Even adding the listItems variable to the global scope (outside of the handler function where the globally defined buttons are), but then you would run into the same problem. The click handlers would be referencing the same instance of the variable. Creating two separate listItems variables (listItems1, listItems2) would help in this scenario, but that isn't very DRY and wouldn't be acceptable in a large codebase.

2.) In asynchronous code

In the example below, what would you expect the final value of groceries to be? I think that most people (myself included) would automatically assume that groceries would be ['Bananas', 'Apples', 'Grapes', 'Peppers']

var groceries = [
  'Bananas',
  'Apples',
  'Grapes',
  'Blueberries'
]

for(var i = 0; i < groceries.length; i++) {
  setTimeout(function() {
    if(i === 3) {
      groceries[i] = "Peppers"
    }
    console.log(groceries)
  }, 2000)
}

If you run the code above, you might be surprised to find that the array outputs exactly the same as the way it was initialized, with no changes to the initial array. What, exactly is going on here?

In the code above, the for loop is finished running before any of the setTimeout callbacks actually run. Therefore, the value desired in the conditional above is never passed to the setTimeout. That function only ever receives the value of the last iteration of the for loop.

To fix this issue, we could just use let to initialize the counter variable in the first argument of the for loop, but that would be no fun at all and it wouldn't help us learn closures (although it does allow for us to become more familiar with scope itself)

In the code below, we use a closure to store the current value of each iteration of the loop into a separate reference.

View the Codepen here.

var groceries = [
  'Bananas',
  'Apples',
  'Grapes',
  'Blueberries'
]

for(var i = 0; i < groceries.length; i++) {
  (function(i) {
    setTimeout(function() {
        if(i === 2) {
          groceries[i] = "Peppers"
        }
        console.log(groceries)

    }, 1000)
  })(i)
}

In the loop above, we use an IIFE pattern to work around our async behavior issue. Here, we pass the i value to the IIFE on line 17. That variable is then entered into the scope where the setTimeout function resides. Now that that reference to the i variable is stored in that scope, it can't be modified by the loop incrementation.

Each iteration of the loop starts a new setTimeout and saves the value if i into it. Now, if you log the values of the groceries, you should see that the third element of groceries is now peppers as intended.

What we did here, was Enclose the current scope of each iteration into its own separate scope. The lexical scope of each iteration has access to the values around it, as a closure should.

Closure

Now that we all understand the basics of closures in Javascript, we can all sense a feeling of closure and resolution. I know, that was awful.
If you have any more questions on the topic, feel free to reach out to me via email at jhurl824@gmail.com. Thank you for reading, and if you learned something from this article today, please share or recommend to a friend. Making everyone better makes life easier for everyone.

A Tech Blog for Everyone