Queen Nnakwue is a technical writer on a mission to make learning the hard stuff fun by writing in the easiest way possible.
Published on
Understanding closures and how they work can allow for a deeper understanding of JavaScript and other programming languages we use that heavily rely on this concept. This explains why so much emphasis is placed on the need to understand closures.
In this article, we will be introducing JavaScript Closures from the ground up. We will start by looking at closures from a high level - what are they, how they work, how to use them in our code, and how they are structured.
We will also look at the scope chain and its use cases. Lastly, we will explore how to emulate private methods with closures effectively.
Prerequisites: To follow through with this tutorial, a basic understanding of the JavaScript function scope and familiarity with the lexical environment is necessary. On the flip side, we will also cover the basics of these, so readers of all background levels can follow suit.
A Closure is a function (an inner function) that has access to another function (an outside function) outside of its scope. It is a function combination created by calling a function inside another function. Closures are used widely in Nodes' single-threaded, event-driven, and non-blocking architecture.
Let us illustrate how this works with an example below:
In the above example, greeting() creates a local variable called message and an inner function called displayMessage(), defined inside greeting() and is available only within the scope of the greeting() function. However, it will interest you to note that displayMessage() can access the variable message declared in its parent function, greeting().
Let’s try this in real-time. Go to your dev tools in your browser and run the above lines of code. Notice that “Hello World” is passed to alert? The reason isn’t far-fetched. Recall we mentioned at the beginning that closures are a combination of functions that have access to each other within the same lexical environment. Following that definition, newMessage refers to the instance of the function displayMessage that is created when the greeting() function is run.
Note: The instance of displayMessage maintains a reference to its lexical environment, within which the variable message exists.
For this reason, when we invoke newMessage, the variable message remains available for use, and "Hello World" is passed to alert.
One of the essential features of closures is that the inner function still has access to and can use the outer function's variables even after the outer function has run or returned.
In JavaScript, functions execute using the same scope chain that was in place when they were created. This means that even after the outer function has returned, the inner function still has access to the outer function’s variables.
This example throws more light:
Further, let's also explore how we can use closure to define a function (an inner function) n inside another function m (an outer function) and expose n. Let us see how we will do so below with a quick example:
We defined a multiply(m) function, which takes a single argument, m, and returns a new function n. The function it returns, in turn, takes a single argument, n, and returns m * n. multiply is used to create two new closures (functions) — multiply9 and multiply5 which contain different lexical scopes although they share the same function body definition. For instance, In the lexical scope of multiply9, m is 9 while in multiply5, m is 5.
Closures are essential as they can control what is and isn’t in the scope of a particular function. They also control which variables sibling functions share in the same lexical scope. Closures can also help simplify our code and make our lives as programmers easier.
For example, we can use closure to store and hide data in a separate scope and share it only when we deem fit — data encapsulation. However, when we use closures for data encapsulation, the enclosed variables are only within the scope of the containing (outer) function. This means that we can’t access the data from an outside environment except through the methods defined in the object.
In the example above, side is inside the scope of the function createSquare, and it’s only visible inside the scope of the function createSquare. This is because variables/parameters are visible only inside its function.
It's also accessible inside the area function since that function is defined inside the function createSquare. This makes area exposed but Diameter is hidden, meaning that the circle encapsulates the side within it.
This is a prevalent use case that we will see in practice and real-world use cases without even knowing its closure in place.
Additionally, closures are also helpful in associating data with a function that operates on that data. This is similar to object-oriented programming, where an object effectively associates the same object's properties with one or more methods.
In JavaScript, every closure has three scopes, namely:
Now let's explore each of them below.
When a variable defined is not inside a function or a pair of curly braces, { } then we can say that such a variable exists in the Global scope and therefore can be accessed from anywhere in the program. For example:
When variables are declared inside a function, we can only access them from within that function, which means we can't access them outside the function scope. For example:
With ECMAScript 2015 (or ES6), let and const keywords, variables can only be scoped to the nearest pair of curly braces. Unlike var variables, we can't access them outside that pair of curly braces. For example:
Private methods/functions in JavaScript are only accessible within their scope. These variables also allow the function defined within their scope to have total control over how they are manipulated.
JavaScript does not give us the luxury to specify whether variables and methods associated with objects should be public or private in their declaration. Still, it does give us a way to implement them, as seen below:
Source: MDN
In the code block above, there is a single lexical environment- counter that is shared by the three functions: counter.increment, counter.decrement, and counter.value. The lexical environment contains two private items: a variable called privateCounter, and a function called changeBy. We cannot access these private members from outside the anonymous function counter. Instead, you can access them using the three public functions returned from the anonymous wrapper. The three public functions are closures that share the same lexical environment and can each access the privateCounter variable and the changeBy function through scoping.
Get visual proof, steps to reproduce and technical logs with one click
Continue reading
Try Bird on your next bug - you’ll love it
“Game changer”
Julie, Head of QA
Try Bird later, from your desktop