DEV Community

Cover image for Closures in Javascript
Kinanee Samson
Kinanee Samson

Posted on

Closures in Javascript

In JavaScript, a closure is an inner function that is able to access the variables of its outer (enclosing) function. The inner function is provided with three separate scopes: access to its own scope, access to the variables of the outer function, and access to the global variables. What are scopes?

When a function is executed in JavaScript, a list of objects is consulted to locate any identifiers (variables) used within the function - this list of objects is known as the scope chain. The scope chain is organized such that the local scope is checked first, then followed by each other scope in the chain. If a variable is not found in the local scope, it will be searched for in the global scope and any scopes that exist between the two. If the variable is not found in any of the scopes, a ReferenceError will be thrown.

function outerFunc() {
  let outerVariable = 10;

  function innerFunc() {
    let innerVariable = 20;
    console.log(outerVariable);
    console.log(innerVariable);
  }

  innerFunc();
}

outerFunc(); // 10, 20
Enter fullscreen mode Exit fullscreen mode

The code snippet above demonstrates how a scope chain works in JavaScript. The outerFunc() function has a local scope, which contains a variable called outerVariable. The innerFunc() function exists within the scope of outerFunc(), and it has its own local scope which contains a variable called innerVariable. When innerFunc() is called, it has access to the outerVariable declared in outerFunc()'s scope, as well as its own innerVariable. This is because of the scope chain - the innerFunc() function can access variables declared in the outerFunc() scope, as well as its own scope. When the code is executed, the outerVariable is logged first, followed by the innerVariable.

How does this relates to closures? The code snippet above is related to closures because the innerFunc() function is a closure. In this example, innerFunc() has access to the outerFunction()'s scope, as well as its own scope, since they are part of the same scope chain. The innerFunc() function can access the outerFunction()'s variables, but the outerFunction() cannot access the innerFunc()'s variables, since they are in a different scope.

function assignApples(owner: string){
  let apples = 0;
  // outer scope, can only use apples
  return function doSomethingWithApple(){
    apples += 1;
    // inner scope, can use str & apples
    let str = `${owner} has ${apples} apples`;
    return str;
  }
}

Enter fullscreen mode Exit fullscreen mode

The example above shows how closures can be used to generate private variables. The assignApples() function takes in an owner parameter and creates a local variable called apples with an initial value of 0. It then returns a function called doSomethingWithApple(), which has access to both the owner parameter (declared in the outer scope) and the apples variable (declared in the inner scope). The doSomethingWithApple() function is a closure, as it has access to the outer scope's variables, and can also modify the inner scope's variables. In this case, the doSomethingWithApple() function can increase the value of the apples variable by 1 each time it is called. The closure also allows the doSomethingWithApple() function to create a string with the owner's name and the number of apples they have, without exposing the apples variable to the outer scope.

const sam = assignApples('sam');
const frodo = assignApples('frodo');

sam(); // sam has 1 apples
frodo(); // frodo has 1 apples
sam(); // sam has 2 apples
Enter fullscreen mode Exit fullscreen mode

How can we use closures

Closures can be used to create variables that can only be accessed and modified within a specific scope. This is useful for protecting sensitive data, such as API keys or user passwords, from being accessed or modified outside of the intended area. By using closures, developers can ensure that any data stored in these private variables remains secure.

function createPasswordHandler(){
  let password = 'SECRET';

  return {
    setPassword: (newPassword: string) => {
      password = newPassword;
    },
    getPassword: () => {
      return password;
    }
  }
}

const passwordHandler = createPasswordHandler();
passwordHandler.getPassword(); // 'SECRET'
passwordHandler.setPassword('NEW SECRET');
passwordHandler.getPassword(); // 'NEW SECRET'
Enter fullscreen mode Exit fullscreen mode

Closures can also be used to create variables that can only be accessed and modified within a specific scope. This is useful for protecting sensitive data, such as API keys or user passwords, from being accessed or modified outside of the intended area. By using closures, developers can ensure that any data stored in these private variables remains secure. Consider a function that adds a certain amount of money to a user's account balance:

function addMoney(amount: number){
  let balance = 0;
  balance += amount;
  return balance;
}
Enter fullscreen mode Exit fullscreen mode

The function above doesn't remember the balance after it is called, so it can't be used to implement undo/redo. However, if we use a closure, we can keep track of the changes that have been made:

function addMoney(amount: number){
  let balance = 0;
  return function(){
    balance += amount;
    return balance;
  }
}

const add5 = addMoney(5);
add5(); // 5
add5(); // 10
add5(); // 15
Enter fullscreen mode Exit fullscreen mode

Closures can be used to create functions that can be passed around and used in different contexts. This is useful for creating reusable components that can be used across different parts of an application. For example, consider a function that creates a counter:

function createCounter(){
  let count = 0;
  return function(){
    count += 1;
    return count;
  }
}
Enter fullscreen mode Exit fullscreen mode

The function above can be used to create any number of counters, which can be used in different parts of the application. For example, it can be used to create a counter for tracking the number of times a user has logged in:

const loginCounter = createCounter();
loginCounter(); // 1
loginCounter(); // 2
loginCounter(); // 3
Enter fullscreen mode Exit fullscreen mode

For example, it can be used to track the number of times a certain feature has been used or the number of times a user has visited a certain page. It can also be used to track the number of items in a user's shopping cart or the number of views a certain video has had. All of these can be tracked using the same counter, which can be easily reused across different parts of the application.

Closures can be used to create private functions that keep the data they contain hidden from the outside world. This is beneficial for creating secure functions that can only be accessed by those who have permission. An example of this is a function that creates a personal data store which is only accessible by certain users.

function createUserStore{
  let data = {};
  return function(key: string, value: any){
    if (value) {
      data[key] = value;
    } else {
      return data[key];
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

The createUserStore function can be used to store data that needs to be kept private from the outside. For example, it can be used to store user passwords or API keys that can only be accessed by certain users. Additionally, it can be used to store confidential data such as financial records or medical records that need to be kept secure.

const userStore = createUserStore();
userStore('userName', 'John Doe');
userStore('password', 's3cr3t');

userStore('userName'); // 'John Doe'
userStore('password'); // 's3cr3t'
Enter fullscreen mode Exit fullscreen mode

To cap our findings today, closures are inner functions that have access to the outer (enclosing) function's variables—scope chain. Closures can be used to create private variables that can only be accessed and modified within a certain scope, create stateful functions that remember the values of the variables declared in the outer scope, create functions that can be passed around and used in different contexts, and create data-hiding functions that keep the data they contain private from the outside. Closures can be used to create secure functions that can only be accessed by certain users, create reusable components that can be used across different parts of an application, and implement features such as undo/redo.

Top comments (0)