Why Closure?

Now that we have a well-rounded sense of what closure is and how it works, let’s explore some ways it can improve the code structure and organization of an example program.

Imagine you have a button on a page that when clicked, should retrieve and send some data via an Ajax request. Without using closure:

  1. var APIendpoints = {
  2. studentIDs:
  3. "https://some.api/register-students",
  4. // ..
  5. };
  6. var data = {
  7. studentIDs: [ 14, 73, 112, 6 ],
  8. // ..
  9. };
  10. function makeRequest(evt) {
  11. var btn = evt.target;
  12. var recordKind = btn.dataset.kind;
  13. ajax(
  14. APIendpoints[recordKind],
  15. data[recordKind]
  16. );
  17. }
  18. // <button data-kind="studentIDs">
  19. // Register Students
  20. // </button>
  21. btn.addEventListener("click",makeRequest);

The makeRequest(..) utility only receives an evt object from a click event. From there, it has to retrieve the data-kind attribute from the target button element, and use that value to lookup both a URL for the API endpoint as well as what data should be included in the Ajax request.

This works OK, but it’s unfortunate (inefficient, more confusing) that the event handler has to read a DOM attribute each time it’s fired. Why couldn’t an event handler remember this value? Let’s try using closure to improve the code:

  1. var APIendpoints = {
  2. studentIDs:
  3. "https://some.api/register-students",
  4. // ..
  5. };
  6. var data = {
  7. studentIDs: [ 14, 73, 112, 6 ],
  8. // ..
  9. };
  10. function setupButtonHandler(btn) {
  11. var recordKind = btn.dataset.kind;
  12. btn.addEventListener(
  13. "click",
  14. function makeRequest(evt){
  15. ajax(
  16. APIendpoints[recordKind],
  17. data[recordKind]
  18. );
  19. }
  20. );
  21. }
  22. // <button data-kind="studentIDs">
  23. // Register Students
  24. // </button>
  25. setupButtonHandler(btn);

With the setupButtonHandler(..) approach, the data-kind attribute is retrieved once and assigned to the recordKind variable at initial setup. recordKind is then closed over by the inner makeRequest(..) click handler, and its value is used on each event firing to look up the URL and data that should be sent.

NOTE:
evt is still passed to makeRequest(..), though in this case we’re not using it anymore. It’s still listed, for consistency with the previous snippet.

By placing recordKind inside setupButtonHandler(..), we limit the scope exposure of that variable to a more appropriate subset of the program; storing it globally would have been worse for code organization and readability. Closure lets the inner makeRequest() function instance remember this variable and access whenever it’s needed.

Building on this pattern, we could have looked up both the URL and data once, at setup:

  1. function setupButtonHandler(btn) {
  2. var recordKind = btn.dataset.kind;
  3. var requestURL = APIendpoints[recordKind];
  4. var requestData = data[recordKind];
  5. btn.addEventListener(
  6. "click",
  7. function makeRequest(evt){
  8. ajax(requestURL,requestData);
  9. }
  10. );
  11. }

Now makeRequest(..) is closed over requestURL and requestData, which is a little bit cleaner to understand, and also slightly more performant.

Two similar techniques from the Functional Programming (FP) paradigm that rely on closure are partial application and currying. Briefly, with these techniques, we alter the shape of functions that require multiple inputs so some inputs are provided up front, and other inputs are provided later; the initial inputs are remembered via closure. Once all inputs have been provided, the underlying action is performed.

By creating a function instance that encapsulates some information inside (via closure), the function-with-stored-information can later be used directly without needing to re-provide that input. This makes that part of the code cleaner, and also offers the opportunity to label partially applied functions with better semantic names.

Adapting partial application, we can further improve the preceding code:

  1. function defineHandler(requestURL,requestData) {
  2. return function makeRequest(evt){
  3. ajax(requestURL,requestData);
  4. };
  5. }
  6. function setupButtonHandler(btn) {
  7. var recordKind = btn.dataset.kind;
  8. var handler = defineHandler(
  9. APIendpoints[recordKind],
  10. data[recordKind]
  11. );
  12. btn.addEventListener("click",handler);
  13. }

The requestURL and requestData inputs are provided ahead of time, resulting in the makeRequest(..) partially applied function, which we locally label handler. When the event eventually fires, the final input (evt, even though it’s ignored) is passed to handler(), completing its inputs and triggering the underlying Ajax request.

Behavior-wise, this program is pretty similar to the previous one, with the same type of closure. But by isolating the creation of makeRequest(..) in a separate utility (defineHandler(..)), we make that definition more reusable across the program. We also explicitly limit the closure scope to only the two variables needed.