Mastering Closures in JavaScript: A Comprehensive Guide
Tags: javascript, node, closures
Published: 2023-09-26 09:41:37

Closures are a fundamental part of JavaScript, providing an excellent tool for writing more maintainable and modular code. In this guide, we'll dive into the depths of closures, exploring their characteristics, uses, and the potential pitfalls and optimisations related to them.


&nbsp; &nbsp;

## Scoping in JavaScript

In JavaScript, scoping refers to the context in which values and expressions are "visible" or can be referenced. If a variable or other expression is not "in the current scope", then it is unavailable for use. There are two main types of scope in JavaScript: global scope and local scope, and with the introduction of ES6, block scope came into the picture as well.

&nbsp;
### Global Scope

When a variable is declared outside of a function, it is in the global scope and its value is accessible and modifiable throughout the program. Example:

```javascript
var myGlobalVar = "This is global!";

function printSomething() {
  console.log(myGlobalVar);
}

printSomething(); // Output: This is global!
```

&nbsp;
### Local (or Function) Scope

Variables declared within a function are in the local scope, and they cannot be accessed outside of that function. Example:

```javascript
function printSomething() {
  var myLocalVar = "I'm local!";
  console.log(myLocalVar);
}

printSomething(); // Output: I'm local!

// Not allowed
console.log(myLocalVar); // Output: Uncaught ReferenceError: myLocalVar is not defined
```

&nbsp;
### Block Scope

Introduced in ES6, `let` and `const` allow for block-level scoping, meaning the variable is only accessible within the block it’s defined. Example:

```javascript
if (true) {
  let blockScopedVar = "I'm block scoped!";
  console.log(blockScopedVar); // Output: I'm block scoped!
}

console.log(blockScopedVar); // Uncaught ReferenceError: blockScopedVar is not defined
```



As we've seen, when we define a function, variables defined within the function are only available from within the function. Any attempt to access those variables outside the function will result in a scope error. This is where closures (lexical scoping) comes in!

&nbsp; &nbsp;

## What are Closures in JavaScript?

JavaScript follows lexical scoping for functions, meaning functions are executed using the variable scope that was in effect when they were defined, not the variable scope that is in effect when they are invoked.

A closure is the combination of a function and the lexical environment within which that function was declared. In JavaScript, closures are created every time a function is created, at function creation time.

```javascript
function outerFunction() {
  let outerVar = "I'm from outer function!";
  
  return function innerFunction() {
    console.log(outerVar);
  }
}
```

In the example above, as a result of function local scoping, the `innerFunction()` is not available from the global scope.  The `innerFunction()` can access all the local variables defined in `outerFunction()` because it is defined within the `outerFunction()`. The `innerFunction()` retains access to the `outerVariable` even outside its execution context.

The `innerFunction()` is still technically local to the `outerFunction()`. However, we can access the `innerFunction()` through the `outerFunction()`  because it is returned from the function. Calling the `outerFunction()` gives us access to the `innerFunction()`:

```javascript
const closureFunction = outerFunction();
closureFunction(); // Output: I'm from outer function!
```

&nbsp; &nbsp;
## Practical Use Cases of Closures

Closures have several practical use cases in JavaScript development. Closures are commonly used to create factory functions, encapsulate data, and manage state in callbacks and event handlers.

&nbsp;
### Data Privacy/Encapsulation

Closures can be used to create private variables or methods, which is a fundamental aspect of Object-Oriented Programming (OOP). By using a closure, you can create variables that can't be accessed directly from outside the function, providing a way to encapsulate data.

Example:

```javascript
function createBankAccount(initialBalance) {
  let balance = initialBalance;
  return {
    getBalance: function() {
      return balance;
    },
    deposit: function(amount) {
      balance += amount;
      return balance;
    },
    withdraw: function(amount) {
      if (amount > balance) {
        console.log('Insufficient funds');
        return;
      }
      balance -= amount;
      return balance;
    },
  };
}

const account = createBankAccount(100);
console.log(account.getBalance()); // 100
account.deposit(50);
console.log(account.getBalance()); // 150
account.withdraw(30);
console.log(account.getBalance()); // 120
```

In this example, a `createBankAccount` function is defined that takes an initial balance as a parameter. It initializes a `balance` variable and returns an object with three methods (`getBalance`, `deposit`, `withdraw`) that can access and modify the `balance`.

- `balance` is not accessible directly from outside the function, ensuring data privacy.
- `getBalance` allows you to view the current balance.
- `deposit` adds a specified amount to the `balance`.
- `withdraw` subtracts a specified amount from the `balance`, if sufficient funds are available.

When `createBankAccount` is called, it returns an object with methods that have access to the `balance`, even after the `createBankAccount` function execution context is gone. This is an example of a closure.


&nbsp;
### Maintaining State

Closures are a great way to maintain state between function calls. This characteristic is particularly useful when working with asynchronous code, event handlers, or any situation where you need to preserve a specific state over time.

Example:

```javascript
function createCounter() {
  let count = 0;
  return function() {
    count += 1;
    console.log(count);
  };
}

const counter = createCounter();
counter(); // Output: 1
counter(); // Output: 2
counter(); // Output: 3
```

In this example, the `counter` function maintains the `count` state between different calls.

- `createCounter` initialises a `count` variable and returns an anonymous function.
- Each time the returned function is called, it increments `count` and logs the value.
- `count` retains its value between calls to the returned function, allowing it to act as a persistent counter.


&nbsp;
### Factory Functions

Closures can be used to create factory functions, which return objects with methods and properties that have access to the private data within the closure.

Example:

```javascript
function personFactory(name, age) {
  return {
    getName: function() {
      return name;
    },
    getAge: function() {
      return age;
    },
    celebrateBirthday: function() {
      age += 1;
    },
  };
}

const john = personFactory('John', 30);
console.log(john.getName()); // Output: John
console.log(john.getAge()); // Output: 30
john.celebrateBirthday();
console.log(john.getAge()); // Output: 31
```

- `personFactory` takes `name` and `age` parameters and returns an object with three methods (`getName`, `getAge`, `celebrateBirthday`).

- Each method has access to `name` and `age`, demonstrating closure behavior.

- `celebrateBirthday` method can modify the `age`, illustrating how closures can update the enclosed variables.

  
&nbsp;
### Partial Application and Currying

Closures can be used to implement partial application and currying, which are techniques in functional programming.

Example:

```javascript
function multiply(a, b) {
  return a * b;
}

function partialMultiply(a) {
  return function(b) {
    return multiply(a, b);
  };
}

const double = partialMultiply(2);
console.log(double(5)); // Output: 10
```

- `partialMultiply` takes a single parameter and returns a function.
- The returned function takes another parameter and calls `multiply`, which takes two parameters.
- This is a simple example of partial application, where a function that takes multiple parameters is broken into multiple functions that take one parameter each.


&nbsp;
### Event Handling

Closures are used in event handling to encapsulate the state and provide access to variables across different scopes.

Example:

```javascript
function setupButtons() {
  for (var i = 0; i < 3; i++) {
    document.getElementById('button' + i).addEventListener('click', (function(i) {
      return function() {
        alert('Button ' + i + ' clicked');
      };
    })(i));
  }
}
```

- A loop is used to add event listeners to buttons.
- An immediately-invoked function expression (IIFE) is used to create a closure that captures the current value of `i` for each iteration.
- This ensures that each button click alerts the correct button number.


&nbsp;
### Timeouts and Intervals

Closures are used in `setTimeout` and `setInterval` to refer to the correct variable or state.

Example:

```javascript
function delayedAlert(number) {
  setTimeout(function() {
    alert('Number: ' + number);
  }, number * 1000);
}

delayedAlert(5);
```

- `setTimeout` is used to delay the execution of a function.
- The function passed to `setTimeout` has access to the `number` parameter, demonstrating a closure.
- The alert will display the correct number even after a delay.



In each of these use cases, closures provide a way to manage and encapsulate state, allowing for more modular and maintainable code.


&nbsp; &nbsp;
## Exploring Advanced Closure Patterns

In the world of JavaScript, closures are a pivotal concept that allows functions to access variables from an outer function even after the outer function has executed. Beyond their basic use, there are advanced closure patterns that developers can use to create more efficient, readable, and maintainable code. This section delves into some of these advanced closure patterns and provides practical examples to illustrate their utility.

Advanced closure patterns in JavaScript are techniques that involve utilising closures to accomplish specific, often sophisticated, programming tasks. These patterns can enhance code modularity, encapsulation, and even performance.


&nbsp;
### 1. The Module Pattern

The Module Pattern is a structural pattern that uses closures to create private and public encapsulated variables and methods within a single object.

Example:

```javascript
const Calculator = (function() {
  let _data = 0; // private variable
  
  function add(number) { // private method
    _data += number;
    return this;
  }
  
  function fetchResult() { // public method
    return _data;
  }
  
  return {
    add,
    fetchResult
  };
}());

Calculator.add(5).add(3);
console.log(Calculator.fetchResult()); // Output: 8
```



#### What's Happening?

- A `Calculator` object is created using an Immediately Invoked Function Expression (IIFE).

- Within the IIFE, `_data` is a private variable that is inaccessible outside of the function scope.

- The `add` function takes a `number` as a parameter and adds it to `_data`. It returns `this`, allowing for method chaining (e.g., `Calculator.add(5).add(3)`).

- The `fetchResult` function returns the current value of `_data`.

- `add` and `fetchResult` are exposed to the external scope by being returned in an object.

  

#### Why is this Useful?

- It provides a way to encapsulate private variables (`_data`) and methods (`add`), ensuring they cannot be accessed or modified externally.
- The `fetchResult` method provides controlled access to `_data`.


&nbsp;
### 2. The Factory Function Pattern

The Factory Function Pattern uses closures to encapsulate and return object instances with methods and properties, allowing for the creation of similar objects without explicitly class-based syntax.

Example:

```javascript
function personFactory(name, age) {
  return {
    getName: function() {
      return name;
    },
    celebrateBirthday: function() {
      age += 1;
    },
    getAge: function() {
      return age;
    }
  };
}

const john = personFactory('John', 30);
john.celebrateBirthday();
console.log(john.getAge()); // Output: 31
```



#### What's Happening?

- `personFactory` is a function that takes `name` and `age` parameters.

- It returns an object with three methods (`getName`, `celebrateBirthday`, `getAge`) that have access to `name` and `age`.

- `john` is an instance of a person created by calling `personFactory`.

  

#### Why is this Useful?

- It allows for the creation of multiple objects (`person`) without explicitly defining classes.
- Encloses `name` and `age` within each created object, ensuring they cannot be accessed or modified directly from the outside.


&nbsp;
### 3. The Revealing Module Pattern

The Revealing Module Pattern is a variation of the Module Pattern where only the necessary variables and methods are exposed, maintaining the encapsulation of all other components.

Example:

```javascript
const ArrayAnalyzer = (function() {
  function average(array) {
    const sum = array.reduce((acc, num) => acc + num, 0);
    return sum / array.length;
  }
  
  function maximum(array) {
    return Math.max(...array);
  }
  
  return {
    average: average,
    maximum: maximum
  };
}());

console.log(ArrayAnalyzer.average([1, 2, 3, 4])); // Output: 2.5
```



#### What's Happening?

- `ArrayAnalyzer` is an object created using an IIFE.
- It has two private functions, `average` and `maximum`, that are exposed as public methods in the returned object.



#### Why is this Useful?

- It keeps the internal workings (calculations of average and maximum) encapsulated and exposes only the necessary interface.
- Enhances code readability and maintainability.


&nbsp;
### 4. Partial Application

Partial Application is a pattern where a function that takes multiple arguments is transformed into a series of functions each taking a single argument.

Example:

```javascript
function multiply(a, b) {
  return a * b;
}

function partialMultiply(a) {
  return function(b) {
    return multiply(a, b);
  };
}

const triple = partialMultiply(3);
console.log(triple(5)); // Output: 15
```



#### What's Happening?

- `partialMultiply` is a higher-order function that takes a single argument `a` and returns a new function.
- The returned function takes another argument `b` and calls `multiply`, passing in both `a` and `b`.



#### Why is this Useful?

- It allows for the creation of new functions with preset arguments, enhancing code reusability and readability.



Advanced closure patterns like the Module Pattern, Factory Function Pattern, Revealing Module Pattern, and Partial Application leverage the power of closures to achieve various programming goals, from encapsulation to functional transformations. Understanding and implementing these patterns allow developers to write robust, efficient, and clean JavaScript code, enhancing the structure and manageability of their applications.


&nbsp; &nbsp;
## Closures and Memory

Closures provide a powerful and flexible way to work with functions and variables. However, it’s important to have a deep understanding of how closures interact with memory to ensure efficient code performance. In this section, we will discuss how they can impact memory usage, and provide strategies for optimising memory management when working with closures.


&nbsp;
### Closures and Memory Consumption

Closures retain their surrounding lexical context, which can sometimes lead to increased memory consumption or memory leaks if not managed correctly. Each time a closure is created, it holds a reference to its outer scope, preserving the variables and preventing them from being garbage collected.

Example:

```javascript
function closureCreator() {
  let largeArray = new Array(10000).fill({});
  return function() {
    console.log(largeArray.length);
  }
}

const myClosure = closureCreator();
```

In this code, `largeArray` is retained in memory as long as `myClosure` exists because it has access to the scope of `closureCreator`. If multiple closures like this are created, it can quickly lead to high memory usage.


&nbsp;
### Identifying and Preventing Memory Leaks

Strategies to manage memory when working with closures:

**1. Avoid Unnecessary Variable Retention**: Ensure that closures do not unnecessarily retain variables that are not used or needed beyond the life of the function.

**2. Properly Release References**: Explicitly nullify or delete references to objects or variables that are no longer needed.

```javascript
function optimizedClosureCreator() {
  let largeArray = new Array(7000).fill({});
  return function() {
    console.log(largeArray.length);
    largeArray = null; // release the reference
  }
}
```

**3. Utilise Weak References**: Consider using `WeakMap` or `WeakSet` to store references to objects, as these data structures do not prevent their objects from being garbage collected.

```javascript
const weakMap = new WeakMap();
function weakMapClosureCreator(obj) {
  weakMap.set(obj, { key: 'value' });
  return function() {
    console.log(weakMap.get(obj));
  }
}
```

**4. Be Cautious With Event Listeners**: Ensure to remove event listeners that use closures when they are no longer needed.

```javascript
function setupButton() {
  const button = document.getElementById('myButton');
  const onClick = () => {
    // handle click
    button.removeEventListener('click', onClick);
  };
  button.addEventListener('click', onClick);
}
```



Closures can also impact the execution speed of the code. Accessing variables outside of the immediate function scope is generally slower than accessing variables within the same scope. We can improve execution speed by:

- Minimising the depth of scope chain access, avoiding deeply nested closures where possible.

- Caching frequently accessed variables outside the closure to avoid repeated scope chain traversal.

  

Understanding the interaction between closures and memory is essential for writing efficient JavaScript code. While closures offer many benefits, it's crucial to manage their memory usage effectively to ensure optimal application performance. Employ the strategies and tools outlined above to write clean, efficient, and memory-optimised JavaScript code involving closures.


&nbsp; &nbsp;
## Closures in Modern JavaScript (ES6 and Beyond)

In modern JavaScript, the concept of closures remains a fundamental aspect of the language, enabling functional programming patterns, data encapsulation, and dynamic function generation. With the introduction of ECMAScript 6 (ES6) and subsequent versions, JavaScript has become more robust, providing new features and syntax that further leverage closures. This section explores closures in the context of modern JavaScript, discussing its applications and demonstrating modern syntax and patterns.


&nbsp;
### ES6 Enhancements

With ES6, the syntax and functionality around closures have seen improvements. Some notable features include:


&nbsp;
#### Introduction of `let` and `const`

The introduction of `let` and `const` in ECMAScript 2015 (ES6) has had a significant impact on the way developers work with closures in JavaScript. Prior to ES6, `var` was the only keyword available for variable declaration, which has function scope. This led to issues and confusion, especially inside loops and blocks, affecting closures unintentionally. The `let` and `const` keywords address these issues by providing block scope to variables.


&nbsp;
##### Block Scoping and Closures

Block-scoped variables (`let` and `const`) significantly influence closures, especially within loops and block statements. Consider this example with `var`:

```javascript
for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, i * 1000);
}
// Outputs: 5, 5, 5, 5, 5
```

In this example, the `setTimeout` function creates a closure that captures the `i` variable, which is function-scoped. By the time the `setTimeout` callbacks execute, the loop has already finished executing, and `i` is `5`.

Now consider using `let` in the same scenario:

```javascript
for (let i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, i * 1000);
}
// Outputs: 0, 1, 2, 3, 4
```

In this example, `i` is block-scoped due to the `let` keyword, meaning each iteration of the loop has its own `i` variable. The `setTimeout` callback captures the `i` variable from each iteration, outputting the expected results.


&nbsp;
##### `const` and Immutable Closure States

`const` is another block-scoped variable declaration introduced in ES6, used for declaring variables whose values should not be reassigned.

```javascript
function outer() {
  const message = 'Hello, World!';
  return function inner() {
    console.log(message);
  };
}

const closure = outer();
closure(); // Outputs: 'Hello, World!'
```

In this example, the `message` variable is enclosed within the returned `inner` function. Because `message` is declared with `const`, it cannot be reassigned, ensuring the state captured by the closure remains immutable.



The introduction of `let` and `const` in ES6 significantly enhances working with closures in JavaScript. By providing block-scoping and ensuring immutability (`const`), these keywords offer more control and predictability, making it easier to create reliable and bug-free closures. Developers should prefer using `let` and `const` over `var` to benefit from these advantages while working with closures and other aspects of JavaScript.


&nbsp;
#### Arrow Functions

Arrow functions provide a more concise syntax for function declaration, and they inherently create closures, carrying the scope of where they are defined.

```javascript
const outer = () => {
  let outerVar = 'I am from outer function';
  const inner = () => console.log(outerVar);
  return inner;
};

const closureFunction = outer();
closureFunction(); // Output: 'I am from outer function'
```

Arrow functions do not have their own `this` context, so `this` inside an arrow function always refers to the enclosing execution context, which is often a desirable behaviour for closures.


&nbsp;
#### Default Parameters

ES6 allows default parameters in function definitions, which can be used effectively with closures.

```javascript
const greet = (name = 'Guest') => () => `Hello, ${name}!`;

const greetUser = greet('User');
console.log(greetUser()); // Output: 'Hello, User!'
```


&nbsp;
### Closures in Asynchronous Operations

Modern JavaScript, especially in Node.js and frontend frameworks, heavily employs asynchronous operations. Closures prove to be invaluable in handling asynchronous tasks, especially with callbacks and promises.

```javascript
function fetchData(url) {
  return fetch(url)
    .then(response => response.json())
    .then(data => () => data);
}

fetchData('https://api.example.com/data')
  .then(getData => console.log(getData()));
```

In the above example, `fetchData` fetches data from a URL and returns a closure that, when invoked, returns the fetched data.



Closures continue to be a fundamental and powerful feature in modern JavaScript, complementing the language’s asynchronous and functional nature. Understanding and efficiently using closures with modern syntax and features allows for cleaner, more efficient, and more robust JavaScript code.

&nbsp; &nbsp;

## Conclusion

Understanding closures is crucial for writing efficient and effective JavaScript code. This guide serves as a comprehensive overview, from the basics and practical use cases to debugging and performance considerations in using closures. Embrace closures, understand their behavior, and utilize them to write cleaner, more modular, and maintainable JavaScript code.



Cover Image by [CopyrightFreePictures](https://pixabay.com/users/copyrightfreepictures-203/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=736400) from [Pixabay](https://pixabay.com//?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=736400)