Closures in JavaScript - JavaScript Closures - Scope and Closures Javascript - Tech tutorial - Udacity Instructor Series

Closures in JavaScript.

A closure refers to the combination of a function and the lexical environment in which that function was declared. Every time a function is defined, closure is created for that function.

But when it comes to day-to-day development, what does this actually mean? What does a closure look like, and how can we best leverage it in JavaScript?

In order for us to see the value and impact of closures on the code we write, let’s first take a look at scope.

Scope.

When a function in JavaScript is run, it creates a new runtime scope. The runtime scope describes the set of variables (i.e., identifiers) available for that function to use. This set of variables includes:

  1. The function’s own arguments
  2. Local variables declared within the function itself
  3. Variables from its parent function’s scope
  4. Global variables

Consider the following example:

// Global variable
const a = "a";
 
function parent() {
 // Variable in the parent() function's scope
 const b = "b";
 
 function child() {
   // Local variable declared in the child() function
   const c = "c";
 }
}

In the above example, the child()function has access to all a,b, and c variables. That is, these variables are in the child() function’s scope. What’s more: if the parent() function accepted any arguments, its nested child() function would also have access to those arguments.

Now the question is: how is this all possible? How does the JavaScript engine actually go upon accessing these variables?

The scope chain.

Under the hood, whenever your code attempts to access a variable during a function call, the JavaScript engine will always start off by looking within the function’s own local variables. If the variable isn’t found, the search will continue looking up what is called the scope chain.

In the example below, when one() is called, all the other nested functions will be called as well (all the way to  three()):

function one() {
 const greeting = "hello";
 
 two();
 
 function two() {
   three();
 
   function three() {
     console.log(greeting);
   }
 }
}
 
one(); // "hello"

You can visualize the scope chain moving “outwards” starting at the innermost level: from three(), to two(), to one(), and then finally to the window/global object. This way, the function three() will have access to any variables and functions “above” it (i.e., those of two() and one()), as well as any global variables defined outside of one(). As such, when three()accesses greeting via its scope chain, “hello” is printed to the console.

Now that we’ve seen what makes up a function’s scope, let’s find out how can we use it to our advantage in the code we write.

Functions retain their scope.

Identifier lookup and the scope chain are powerful tools for a function to access identifiers in the code. In fact, they let you do something really interesting: create a function now, package it up with some variables, and save it to run later.

Consider the remember() function below:

function remember(number) {
 return function () {
   return number;
 };
}
 
const returnedFunction = remember(5);
 
console.log(returnedFunction()); // 5

When the JavaScript engine enters remember(), it creates a new execution scope that points back to the prior runtime scope. This new scope includes a reference to the number parameter. When the engine reaches the inner function, it attaches a link to the current execution scope.

This process of a function retaining access to its scope is called a closure. In this example, the inner function “closes over” number. Since a closure can capture any amount of parameters and variables that it needs, a closure is really a combination of:

  • The function itself, and
  • The code (but more importantly, the scope chain) where the function is declared. That is, the function’s lexical environment

When remember(5); is executed and returned, the returned function is still able to access the value of  number (i.e., 5). Since the function locks onto the scope chain, a closure allows us to store a snapshot of state at the time the function object is created. In fact, a function will retain its scope chain – even if it is invoked in a location other than where it was declared. Again, this is all due to the closure!

Closures and private state.

To recap, we’ve seen two common and powerful applications of closures:

  1. Implicitly passing arguments (i.e., via the scope chain)
  2. At function declaration, storing a snapshot of scope

Another way we can leverage closures is to create “private” variables (i.e., private state). By creating private variables, we can limit the ability of other code in our app to be able to mutate that data.

Consider the following example:

function expandArray() {
 let myArray = [];
 
 return function () {
   myArray.push(0);
   return myArray;
 };
}
 
let returnedFunction = expandArray();

In the example above, expandArray() simply returns a function that adds a new element to myArray, and then returns the current value of myArray

What makes expandArray() so powerful is that it has a private but mutable state. That is, the value of myArray can be updated as needed, but the variable itself cannot be accessed externally because the inner function closes over myArray

As such, the array expands as we keep invoking the returned function:

console.log(returnedFunction()); // [0]
console.log(returnedFunction()); // [0, 0]
console.log(returnedFunction()); // [0, 0, 0]

We know that the data is retained and can also be modified, but there’s no way that anything outside the closure itself can access myArray.

returnedFunction.myArray; // undefined
myArray; // Uncaught ReferenceError: myArray is not defined

Having private state can benefit the applications that we build, since users wouldn’t be able to accidentally perform unwanted operations on that data. Due to closure, there’s no way that that data can be accessed externally!

Learn more about scope and closures. 

In JavaScript, scope and closures go hand-in-hand. Every time a function is defined, closure is created for that function. Strictly speaking, then, every function has closure! This is because functions close over at least one other context along the scope chain: the global scope. However, the capabilities of closures really shine when working with functions defined within another function.

For a closer look at scope and closures in JavaScript, check out these resources:

Or explore the Intermediate JavaScript Nanodegree program to master the most popular programming language in the world.

START LEARNING