DEV Community

Brendon O'Neill
Brendon O'Neill

Posted on

Understanding JavaScript Closures

What is a Closure

From MDN:

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives a function access to its outer scope. In JavaScript, closures are created every time a function is created, at function creation time.

Closures are one of those subjects in JavaScript like recursion that takes a little bit of time to understand and wrap your head around. But below I will try to explain closures in a simple way so you too can use them in your code.

Example 1

function outerFunction(){
  let counter = 0;

  function innerFunction(){
    counter++;
    console.log("Counter: ",counter); // Counter: 1
  }

  return innerFunction;
}

const count = outerFunction();
count();
Enter fullscreen mode Exit fullscreen mode

Above we have a basic closure that adds 1 to our counter in the outerFunction function. So how does a closure work?

When we get to this line in the code:

const count = outerFunction();
Enter fullscreen mode Exit fullscreen mode

It is like when you set a function on a variable like a function expression.

const count = function innerFunction(){
    counter++;
    console.log("Counter: ",counter);
  }
Enter fullscreen mode Exit fullscreen mode

Arrow function:

const count = innerFunction() => {
    counter++;
    console.log("Counter: ",counter);
  }
Enter fullscreen mode Exit fullscreen mode

But in closures we bring the function over as well as the Lexical scope variables (variables in the outerFunction).

So something like this:

const count = function innerFunction(){
    /*
    outerFunction lexical scope:
    counter = 0
    */
    counter++;
    console.log("Counter: ",counter);
  }
Enter fullscreen mode Exit fullscreen mode

When you call count() it will do the same as any other function, it will create an execution context and when it gets to the counter++ (counter = counter + 1) it will check the local scope for counter. When it can't find counter it will look to the outer scope. This is where the closures come in, before it looks at the global scope it will first check its surrounding lexical scope (outerFunction) for counter and because we brought the outerFunction lexical scope variables with us we will have counter available to us.

 const count = outerFunction();
 count(); // 1
Enter fullscreen mode Exit fullscreen mode

And if we call count again.

 const count = outerFunction();
 count(); // 1
 count(); // 2
Enter fullscreen mode Exit fullscreen mode

This is very helpful when you want to have multiple counters but don't want to flood your global scope with variables. A nice way someone explained it to me was like a backpack on the functions that if it couldn't find it locally it can check the backpack for the function.

function outerFunction(){
  let counter = 0;

  function innerFunction(){
    counter++;
    console.log("Counter: ",counter); // Counter: 1
  }

  return innerFunction;
}

const counter1 = outerFunction();
const counter2 = outerFunction();
counter1(); // 1
counter1(); // 2
counter2(); // 1
Enter fullscreen mode Exit fullscreen mode

Each counter has its own separate outerFunction lexical scope variables.

Why are they helpful

Closures create private variables that can only be accessed by themselves or other inner functions within the function. When I was first learning about it I was like why don't I just use an object like this:

let count = {
    counter: 0,
    addCount: function (){
        this.counter++;
        console.log(this.counter);
    }
}

count.addCount() // 1
Enter fullscreen mode Exit fullscreen mode

But a big problem with this is that a user could just call counter on this object and they could update counter.

let count = {
    counter: 0,
    addCount: function (){
        this.counter++;
        console.log(this.counter);
    }
}

count.counter = 12
count.addCount; //13
Enter fullscreen mode Exit fullscreen mode

With closures you can't access the counter variable outside the closure.

function outerFunction(){
  let counter = 0;

  function innerFunction(){
    counter++;
    console.log("Counter: ",counter);
  }

  return innerFunction;
}

const counter1 = outerFunction();
counter1(); // 1
console.log(counter1.counter); // undefined
Enter fullscreen mode Exit fullscreen mode

This is similar to private variables on classes where you can create a method to update the variable or return the value of the variable.

You also don't have to write the function separately from the return you can just return the function.

function outerFunction(){
  let counter = 0;

 return function innerFunction(){
    counter++;
    console.log("Counter: ",counter);
  }
}
Enter fullscreen mode Exit fullscreen mode

You can also use anonymous functions but I would highly recommend you don't, as when you are debugging without named functions, it will be harder to figure out where the error has occurred in the closure when looking at the call stack.

With this you have a basic understanding of closures I hope you understand them a bit better. I have provided video links at the bottom if you would like a more visual explanation, but if you want to learn a bit more we can dive deeper as they can become more powerful.

Returning objects

We don't have to return only functions to get this lexical-scoped memory. We can also return objects with a function to do different things.

function outerFunction(){
  let counter = 0;

  return {
    increase : () =>{
        counter++
        console.log(counter)
    },
    decrease : () =>{
        counter--
        console.log(counter)
    }
  }
}

const counter1 = outerFunction();

counter1.increase() // 1
counter1.increase() // 2
counter1.decrease() // 1

Enter fullscreen mode Exit fullscreen mode

You can also chain closure functions. This is where nested functions can access outer functions' variables, creating a chain of function scopes.

Example

function player(){
  let health = 10;

  return {
    orc: () =>{
        let type = "Orc"
        let attack = 6
        let defense = 6
        return {
            showAttackAndDefense : () => {
                console.log(`Attack: ${attack} || Defense: ${defense}`)
            },
            takeDamage : () => {
                health = health - 3
                console.log(`The ${type} took damage, Health: ${health}`)
            },
        }
    },
    wizard: () =>{
        let type = "Wizard"
        let attack = 8
        let defense = 2

        return {
            showAttackAndDefense : () => {
                console.log(`Attack: ${attack} || Defense: ${defense}`)
            },
            takeDamage : () => {
                health = health - 5
                console.log(`The ${type} took damage, Health: ${health}`)
            },
        }
    },
    knight: () => {
        let type = "Knight"
        let attack = 5
        let defense = 10

        return {
            showAttackAndDefense : () => {
                console.log(`Attack: ${attack} || Defense: ${defense}`)
            },
            takeDamage : () => {
                health = health - 1
                console.log(`The ${type} took damage, Health: ${health}`)
            },
        }
    }
  }
}

const player1 = player()
const player2 = player()
const player3 = player()

const wizard = player1.wizard()
const orc = player2.orc()
const knight = player3.knight()

knight.takeDamage() // The Knight took damage, Health: 9
orc.takeDamage() // The Orc took damage, Health: 7
wizard.showAttackAndDefense() // Attack: 8 || Defense: 2
Enter fullscreen mode Exit fullscreen mode

As you can see above, when knight.takeDamage() was called we got variables from its local scope for type and its outer scope for its health. If we had a health variable in the local scope it would have taken that instead of the lexical outer scope.

If you like a more in-depth explanation, you can read about closures in the MDN documentation

Helpful Resources

Here are two great videos that also explain closures in detail for the visual learners.
JavaScript Closures Tutorial (Explained in depth) by ColorCode
JavaScript Visualized - Closures by Lydia Hallie

Conclusion

Closures are hard to understand, but once you get them they are another tool in your box. I tried to keep away from the overly technical language so people of all levels can understand it.

Have a nice day.

Top comments (0)