Closures in JavaScript: What they are, how they work, and why you should care!
Brunno Marchetti
Posted on February 25, 2024
TL;DR: Closures can be very helpful in creating more interesting and performant code. They are useful for encapsulating data, managing application state, and controlling execution flow in JavaScript.
Just another mundane day at work. Not every day brings excitement or new challenges. Sometimes, it's simply about finding solutions to problems, answering mundane questions, and being pushed to your limits. It's a normal routine for anyone who works to pay their bills. You know how it goes - the constant grind can leave you feeling drained, wishing that the ceiling would collapse and end your suffering.
During one of those days I was having a pairing programming session with one of my mentors, he was explaining the project's architecture to me. As we examined a particular element, he remarked, "This here is nothing special, it's just a Closure." Despite feeling nervous and uncertain, I managed to respond confidently, "Of course..."
As I racked my brain, trying to recall the elusive concept, my mind felt like a never-ending maze. How could I forget something so fundamental? I had heard about "closure" before, but for the life of me, I couldn't recall what it meant. It was frustrating - like trying to grasp sand slipping through my fingers. I knew it was important, but I just couldn't piece it together.
During our programming session, he showed me one of the entry point functions of the application. This function used the lodash once method, which is a highly useful library worth discussing in detail (sometime in the future). The purpose of the method is to ensure that the code is executed only once, preventing any subsequent attempts to execute the same code from having any effect. As a result, it prevents repeated calls to APIs, which ensures optimal performance.
Saving things for later (or, what's a closure for)
All in all, a closure in JavaScript is basically an inner function that has access to the scope of the outer function in which it was defined. Even after the outer function has finished executing, the inner function can still "remember" the environment in which it was created and can access the variables and parameters of that outer function. To put it simply, closures work like this:
In this case, even after the constant closure
was defined with the execution of outerFunction
, making all executions log the string defined in outerVariable
.
A more practical usage
This design pattern can be very useful in various situations. For example, executing specific code only once at the beginning of an application's execution or caching a call to an external API. A simple example of that would be something like the closure shown below:
In this example, the outer function saves the return value of the simulated API in the variable apiData
. When the inner function is executed, it checks the fetchDataOnce
variable to see if any value is stored. If there is a value, the function returns it. Otherwise, it makes a call to the external API.
Pretty cool... and what about the real deal?
A few months ago, I needed to implement a search field that would send a string to the Back End. The Back End would then return a list of results with every user interaction. This means that the call to the server would be triggered by a change in the search field, rather than a click on a search button.
If the phrase "coffee table" is entered in this field without any optimization, it would result in 12 calls to the backend. This is highly unfeasible, and implementing such a functionality would make the team's tech lead extremely unhappy with me.
It's no secret at this point that the solution to this problem was to use a closure. To be more specific, it was the creation of a debouncer:
There you go, the problem is practically solved. The debounce
will receive the function that should be executed and the time it should wait to execute func
. Each time the function is executed, clearTimeout
is called, restarting the timer for execution.
By passing debounceHandleInput
to any event that listens for changes in the search field, handleInput
function will only be executed if no changes are made to the string for 300 milliseconds. This helps to reduce the number of unnecessary requests to zero, or at least significantly reduce them.
In summary, it's a very useful tool to have. However, it may be confusing at first to understand how a function "remembers" the environment in which it was created and accesses external variables. Also, closures can lead to subtle and difficult-to-debug errors if not fully understood, especially with some peculiarities of JavaScript.
Posted on February 25, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 3, 2024