JavaScript Closures with examples

odus_ex

Aditya Tripathi

Posted on September 30, 2023

JavaScript Closures with examples

In JavaScript, Closure is a phenomena of a code getting executed outside its lexical scope. They happen because of writing lexically structured code.

In other words, Closures let us access a foreign parent scope even after it has exited the runtime. Closures are created every-time we create a scope. They are strongly associated with scopes so before exploring Closures, let us refresh what lexical scopes are.

Lexical Scope

You might be already familiar with the concept of Scope and Execution contexts in JavaScript. If you would like a refresher on these topic, consider a recent article I wrote, similar to this: Mastering JavaScript Scopes

Let's cover the important highlights here briefly.

JavaScript associates important execution details of a program with the place they are declared in the source code, pertaining to the visibility of declared entities. At author time, these are called Scopes. At runtime these become Execution Contexts and are called on Execution Stack one after another to hold the information required for successful execution of the code.

Consider the following example:


function greet(name, typeOfGreeting){

let nameInUpper = name.toUpperCase();

function sayHello(){
 const greetings = `Hello ${nameInUpper} :)`
 return greetings;
}

function sayHi(){
 const greetings = `Hi ${nameInUpper}!` 
 return greetings;
}

return typeOfGreeting === 'hi' ? sayHi : sayHello;
}

Enter fullscreen mode Exit fullscreen mode

In the above example, greet function creates a functional scope. This scope contains two other functions, sayHello and sayHi along with greet arguments and variable nameInUpper.

Note that nested scope can access declared entities of the enclosing scope. This means both sayHello and sayHi can access entities of greet (and Global Scope).

However, a parent scope cannot access entities declared in its nested scopes. So greet doesn't have access to entities declared inside sayHi and sayHello.

Also, note that two greetings variables are created without conflict because they are in separate scopes, i.e. one in sayHi and one in sayHello.

Closures in action

Now that we understand scopes, lets explore how closures "happen" as a result of writing lexically scoped code.

When we execute greet like below:


const greetBen = greet("ben", "hi");

console.log(greetBen()) // Hi BEN!

Enter fullscreen mode Exit fullscreen mode

We notice a rather peculiar behaviour. On executing greetBen we are able to remember entities declared in greet and use them at later point of time in code (i.e. in console.log()), even when greet has completed its execution well before executing greetBen.

This is Closures! greetBen is said to have formed a closure on greet. This allows greetBen to keep the entities declared in greet "alive" (i.e. stop being garbage collected), even after its execution context has been popped out of execution call stack.

For better understanding, consider a slightly modified example below:

const greetBen = greet("ben", "hi");

setTimeOut(() => console.log(greetBen()), 5000)
Enter fullscreen mode Exit fullscreen mode

The above code prints the same output as before but after 5 seconds, re-enforcing our understanding of keeping the entities alive and dynamically accessing a them at runtime even when the scope in which they were declared is not available.

Closure Examples

Now that we have seen some basic examples, let us discuss a couple of advanced ones.

Consider the following code:

(function iife1(a) {
  return (function iife2(b) {
    console.log(a); // What is logged?
  })(1);
})(100);
Enter fullscreen mode Exit fullscreen mode

After a good look, we recognise that iife2 forms a closure with iife1 and hence has access to a. The output of the program will be 100.

Consider another code-snippet:

for (var i = 0; i < 3; i++) {
  setTimeout(function print() {
    console.log(i); // What is logged?
  }, 1000);
}
Enter fullscreen mode Exit fullscreen mode

Because var declarations do not create block scopes, the closure formed by print on the enclosing scope (i.e. Global Scope) will get the final updated value of i. Hence the above code will print 3, 3, 3.

If we were to replace var with let, then the enclosing scope for print will be for block instead of the Global Scope, as let creates a block scope. On each loop iteration, a new scope will be created and the updated i value will be generated for each new scope, resulting in sustaining each value as the loop iterates. Hence the output would then be 0, 1 and 2.

I have borrowed these examples from an amazing blog by Dimitri Palvutin. You can find more of such examples by visiting his write-up here.

Design Patterns

JavaScript developers have been utilising Closures to create some of the most famous design patterns in the language. Some of them are discussed below:

Functions as first class citizens

We are able to pass around functions in JavaScript due to closures. A function as argument forms the closure with the calling function.


function doMath(value, operation){
  return operation(value)
}

function addFive(number){
  return number + 5;
}

function multiplyFive(number){
  return number * 5;
}

console.log(doMath(5, addFive)); // 10

console.log(doMath(5, multiplyFive)); // 25

Enter fullscreen mode Exit fullscreen mode

Here, operation function is able to form a closure with doMath enabling us to operate on doMath argument. It is using this behaviour, we are able to implement callbacks and event-listeners in the language.

Module Pattern

Closures are utilised to create data encapsulation and data abstraction. Consider the example below:

function CounterModule(){

// Data Encapsulation
let counter = 0;

function reset(){
 counter = 0
}

function increment(){
 counter++;
}

function decrement(){
 counter--;
}

function getCounter(){
  return counter;
}

return {
  reset,
  increment,
  decrement,
  getCounter
}

}


// Data Abstraction
let {reset, increment, decrement, getCounter} = CounterModule();

}
Enter fullscreen mode Exit fullscreen mode

In the example above, we created a CounterModule with a private implementation detail counter. We then define operations allowed to be performed on this private data by using functions like reset, increment and decrement because these function form closure with CounterModule.

Thats all what Closures are!

I hope that this article helped you understand Closures and how it is utilised in everyday JavaScript code to implement some of the most important language behaviours and characteristics.

Until next time. Happy Hacking! :)

💖 💪 🙅 🚩
odus_ex
Aditya Tripathi

Posted on September 30, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related