Home » Closure in JavaScript

Closure in JavaScript

Closure in JavaScript

Introduction

Closure in Javascript are one of the most important concepts in JavaScript, enabling many of the language’s most powerful features. At their core, closures allow functions to remember the environment in which they were created. This means that a function can access variables from its lexical scope even when it is executed outside that scope. Closures enable powerful programming techniques such as data hiding, currying, and creating factory functions. In JavaScript, closures are a powerful and essential concept that can significantly enhance the flexibility and efficiency of your code. They allow functions to retain access to variables from their containing scope, even after that scope has finished executing. In this article, we’ll dive deep into closures, covering everything from the basics to advanced usage patterns.

Closure

Closure in Javascript is created when an inner function accesses variables from its outer function after the outer function has executed. This inner function “remembers” its lexical scope, forming a closure.

function outerFunction() {
  let outerVariable = 'I am from outer function';

  function innerFunction() {
    console.log(outerVariable);
  }

  return innerFunction;
}

let closureFunc = outerFunction();
closureFunc(); // Output: I am from outer function
JavaScript

In this example, closureFunc is a closure that retains access to outerVariable even after outerFunction has completed execution.

Lexical Scoping

To understand closures, it’s crucial to grasp lexical scoping. Lexical scoping refers to the way variable scope is determined by the physical placement of the variables and functions in the code. In JavaScript, functions are lexically scoped, meaning that the scope is based on where the function is written, not where it is executed.

function outerFunction() {
  let outerVariable = 'I am from outer function';

  function innerFunction() {
    console.log(outerVariable);
  }

  innerFunction(); // Output: I am from outer function
}

outerFunction();
JavaScript

In this example, innerFunction has access to outerVariable because of lexical scoping.

Scoping with let and const

The introduction of let and const in ES6 brought block-level scoping to JavaScript. Unlike var, which has function scope, let and const are scoped to the nearest enclosing block. This makes them more predictable and less prone to errors.

function testScopes() {
  if (true) {
    let blockScoped = 'I am block scoped';
    const anotherBlockScoped = 'I am also block scoped';
    console.log(blockScoped); // Output: I am block scoped
    console.log(anotherBlockScoped); // Output: I am also block scoped
  }
  //if console is out of above scope then the below is the output you will get

  // console.log(blockScoped); // Uncaught ReferenceError: blockScoped is not defined
  // console.log(anotherBlockScoped); // Uncaught ReferenceError: anotherBlockScoped is not defined
}

testScopes();
JavaScript

Using let and const helps prevent variable hoisting issues and makes closures more reliable.

Scope Chain

Closure in JS are closely related to the concept of the scope chain. The scope chain is the mechanism used by JavaScript to look up a reference to a variable. During the referencing of the value of a variable in a program, the JavaScript engine seeks to identify the said variable in the current scope. If there is no such object in the current block of code, it goes up to the next outer scope and repeats it until the global scope is reached.

let globalVar = 'I am global';

function outerFunction() {
  let outerVar = 'I am outer';

  function innerFunction() {
    let innerVar = 'I am inner';
    console.log(globalVar); // Output: I am global
    console.log(outerVar); // Output: I am outer
  }

  innerFunction();
}

outerFunction();
JavaScript

In the given above code, innerFunction has visibility of its parent scope that includes both globalVar and outerVar.

Creating Closures in Loop

Closure in JS are used by many developers, to implement functions inside loops, which is another basic use case of closures. If the closures were omitted, then each of the functions would retain the last value of the variable in the loop, which would be incorrect. They enable each function to store the value that represents the loop index at that time.

function createButtons() {
  for (let i = 1; i <= 3; i++) {
    let button = document.createElement('button');
    button.innerHTML = 'Button ' + i;
    button.addEventListener('click', function() {
      console.log('Button ' + i + ' clicked');
    });
    document.body.appendChild(button);
  }
}

createButtons();
JavaScript

By including let within the loop, it becomes possible to isolate each event handler and provide it with the correct value of i.

To enhance knowledge in closures, there is need to elaborate on its usability as well. Closures also enable data privacy, maintain state, store state between asynchronous operations, and create higher-order functions. Here are some practical examples:Here are some practical examples:

Data Privacy

Closures can be used to create private variables that are not accessible from outside the function.

function createCounter() {
  let count = 0;

  return function() {
    count++;
    return count;
  };
}

let counter = createCounter();
console.log(counter()); // Output: 1
console.log(counter()); // Output: 2
JavaScript

Maintaining State in Asynchronous Operations

Closures help maintain state in asynchronous operations, such as callbacks or promises.

function fetchData(url) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`Data from ${url}`);
    }, 1000);
  });
}

function fetchUrls(urls) {
  urls.forEach((url) => {
    fetchData(url).then((data) => {
      console.log(data);
    });
  });
}

fetchUrls(['url1', 'url2', 'url3']);
JavaScript

Conclusion

It is essential for any developer of software, deeply fun and wonderful to work with closures, and offers abilities that are very profound for establishing and maintaining state. Having knowledge on lexical scoping, scope chain and real life use of closures, allows for better optimization and utilization of declarative codes in JavaScript. Despite the fact that asynchronous operations, creating private variables and functions, function running inside the loops, closures provide one of the most reliable solutions to all these problems.

Frequently Asked Questions

1. What is a Closure in JavaScript?

A closure is a feature in JavaScript where an inner function has access to the outer (enclosing) function’s variables. This includes the outer function’s variables, parameters, and even the global variables. Closures allow functions to retain access to their lexical scope, even when the function is executed outside that scope.

2. Why are Closures Important?

Closures are important because they enable powerful programming patterns and techniques. They allow for data privacy, enable functional programming concepts like currying and partial application, and help manage the state in asynchronous operations. They also facilitate the creation of higher-order functions, which are functions that can return other functions or take them as arguments.

3. Can Closures Lead to Memory Leaks?

Closures can lead to memory leaks if they unintentionally hold references to large objects or variables that are no longer needed. Because closures keep their enclosing scope alive, they can prevent the JavaScript garbage collector from freeing up memory, leading to potential memory leaks. Therefore, to avoid this, ensure that closures do not retain unnecessary references. Additionally, clean up references when they are no longer needed.