Page 5: Another Closure Example

Here is a variant of the example from the last page. I am making two counters that count different things. First, read the code and see if you can understand what it does. Then try it and see if your understanding is correct. Some explanations follow. The files are F-05-01.html and F-05-01.js, but I’ll put the code here so we can refer to it

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// this function transforms a row of buttons
function makeCounters(listOfButtonNames) {
    // keep track of the total presses
    let totalCount = 0;

    // we define the makeCounter function here so it has access to the 
    // total Count
    // the function takes the id of the button
    function makeCounter(buttonName) {
        // these two variables with be inside the closure of the
        // handler function
        const button = document.getElementById(buttonName);
        let count = 0;

        // now we can define the handler that uses those two variables
        // when the function is created, it will enclose those variables
        button.onclick = function() {
            count += 1;         // note that we have access to both count and total count
            totalCount += 1;
            button.innerHTML = `${count} - ${totalCount}`;
        }
    }
    // now we need to apply that function to the buttons, I'll do it
    // with a loop
    for(let buttonName of listOfButtonNames) {
        makeCounter(buttonName);
    }
}

// we can apply this to both rows
makeCounters(["button1","button2","button3","button4","button5"]);
makeCounters(["buttonA","buttonB","buttonC","buttonD","buttonE"]);

And here it is running…

Some things to observe…

  1. I define a function makeCounters that takes a list of buttons (line 2-28). It will change each one to a counter.
  2. Within makeCounters, it defines a variable totalCount - this is one variable for the whole function. It will be shared by everything inside of the function.
  3. Within makeCounters it defines a function makeCounter - you saw this on the last page. But notice, this version has access to both its own variables, but also the variables of its outer block.
  4. Within makeCounter we define a function that is not named, but is assigned to the button’s click handler. You saw this on the last page. The difference here: this function has access to all of its enclosing scopes. So it can access the variables both in makeCounter but also in makeCounters.
  5. Line 20 uses a JavaScript template literal - it basically creates a string with both numbers in it. To learn about template literals check out Template Literals Documentation
  6. Remember, the function in lines 17-21 only gets run when a button is pressed, and only updates the one button that is pressed. This is why when you click a button, only that button updates - but the variables inside get updated for the next button press.
  7. Notice how each row is a separate invocation of makeCounters so it gets its own totalCount variable. Just like each button is a separate invocation of makeCounter so it gets its own counter.

In my mind, this is a useful use of a closure. Yes, I could have written this by creating objects for each button (that store counters) and for each row (to store the “total count”). But that would have required defining two classes of objects, … I find the closure to be concise. But it is a style thing. You rarely need to use a closure - there is always some other way to do it. Closures are just often an elegant solution.

That said, the use of closures for the button variable (line 20) is gratuitious. This inner function probably should have been written:

1
2
3
4
5
    button.onclick = function(event) {
        count += 1;
        totalCount += 1;
        event.target.innerHTML = `${count} - ${totalCount}`;
    }
Because the use of button makes the assumption that button isn’t going to change. But I used a closure because this workbook is about practicing closures.

Closures

That probably really isn’t enough examples. Most students need to see many examples. But hopefully, this will help you get started. The key is to practice. Try using them!