Closures in JS 🔒

Bibi - Jun 26 - - Dev Community

TL/DR:
Closures are a synthetic inner delimiter that has access to its parent's scope, even after the parent function has finished executing.

Not clear? I didn't get it either. Read along partner!


🔒 Closures 🔒

Ah, closures. Sounds pretty straight forward... whatever's inside the curly braces right?! Well, yes and no. This topic is quite simple but there's a lot to explore in it, you'll wonder whether you accidentally stumbled into a parallel universe after reading, I guarantee. But fear not, my fellow wizards, for today we shall unravel the enigma that is closures!

First, let's start with the textbook definition because I love a good MDN reference:

a closure is the combination of a function bundled together with references to its surrounding state (the lexical environment), even after the outer function has returned. Kinda like having the key to a treasure chest of variables, even when you've left the pirate ship!

Okay that wasn't MDN verbatim, but you get the idea. So, how does this sorcery work?? Well, when you create a function within another, the inner one has access to the variables and parameters of the outer function. This is because the inner function forms a closure, maintaining access to the environment in which it was created. It's like the inner function remembers its surroundings!

Here's a classic example to illustrate closures:

1

In this example, outerFunction takes a parameter x and has a local variable y. It defines an innerFunction that accesses both x and y, and then returns the innerFunction. When we assign the result of calling outerFunction(5) to the variable closure, we are essentially capturing the innerFunction along with its environment (where x is 5 and y is 10). Even though outerFunction has finished executing, the closure still remembers the values of x and y, allowing us to invoke it later and get the expected output of 15.

This was just a simple example, but closures actually have various practical applications, such as data privacy, factories, and memoization. They allow you to create functions with private state and encapsulate behaviour, leading to more modular and reusable code. Let's do a deeper dive into these specific applications:

1. Private Variables and Encapsulation 🔒

Closures can be used to create private variables and achieve encapsulation. Consider the following example:

2

In this example, the createCounter function returns an object with an increment method. The count variable is defined within the createCounter function and is not accessible from the outside. The increment method, being a closure, has access to the count variable and can modify it. Each time counter.increment() is called, the count is incremented, but it remains private and cannot be accessed directly from the outside.


2. Function Factories 🏭
Closures can be used to create function factories, which are functions that generate other functions with customized behaviour. Here's an example:

3

In this case, the multiplyBy function takes a factor parameter and returns a new function that multiplies a given number by the factor. We create two separate functions, double and triple, by calling multiplyBy with different factors. Each returned function forms a closure, capturing its own factor value, allowing us to multiply numbers by the respective factors.


3. Memoization
Closures can be used to implement memoization, which is a technique to cache the results of expensive function calls and return the cached result when the same inputs occur again. Here's an example of memoizing a factorial function:

4

In this example, the memoizedFactorial function returns a closure that serves as the actual factorial function. The closure maintains a cache object to store previously computed results. Whenever the factorial function is called with a number n, it first checks if the result is already in the cache. If it is, it returns the cached result. Otherwise, it calculates the factorial recursively and stores the result in the cache before returning it. Subsequent calls with the same n will retrieve the result from the cache, avoiding redundant calculations.


These examples are obviously simple but the applications of closures in JavaScript can be much more complex. They can provide a powerful mechanism for data privacy, code organization, and optimization, when done right!

However, closures can also lead to some gotchas if not used carefully. One common pitfall is creating closures in loops, where the closure captures the last value of the loop variable. But that's a story for another day!


Alright, I think that's it for me ┗(・ω・;)┛

Here are some key points to take home:

  • A closure is a function that remembers the environment in which it was created. It has access to variables and parameters of the outer function.
  • It allows a function to access variables from its outer (enclosing) scope, even after the outer function has finished executing.
  • Closures can access and manipulate variables from the outer scope, even after the outer function has returned.

Hope you learned something with me today! (´◡`)
Bibi

. .