DEV Community

Damien Cosset
Damien Cosset

Posted on

Closures in Javascript

Introduction

You may have heard about closures. You most certainly already use them even if you don't fully know what they are. Closures require you to know:

Three facts:

Fact One: In Javascript, you can refer to variables defined outside of the current function.

function makeCookies(){
    const secretIngredient = "coconut oil"
    function bake(chocolate){
        return secretIngredient + " and " + chocolate
    }

    return bake("white chocolate")
}

makeCookies() // coconut oil and white chocolate

Here, we can see that the inner function bake can access the variable secretIngredient, even though it was defined in the outer makeCookies function.

Fact Two: Functions can refer to variables defined in outer functions even after those outer functions returned!

Because functions are first-class objects, you can store functions inside variables and call them later on. I've talked about higher-order functions in a https://dev.to/damcosset/higher-order-functions-in-javascript-4j8b

function cookiesMaker(){
    const secretIngredient = "coconut oil"
    function bake(chocolate){
        return secretIngredient + " and " + chocolate + " chocolate."
    }

    return bake
}

const func = cookiesMaker() // Storing the function in a variable

Here, cookiesMaker is called and we store the result of that function inside a variable. If you print out the func variable right now, you would see the bake function.

The bake function uses a variable declared outside of the bake function (secretIngredient). The bake function can still remember that variable even if cookiesMaker has already returned.

func("black") // coconut oil and black chocolate.
func("white") // coconut oil and white chocolate.

How is this possible? Well, in Javascript, function values do not just store the code required to execute when they are called. They also store any references to variables they need to execute. Functions like the bake function, who refer to variables declared in their containing scopes are known as closures.

The bake function here keeps track of two variables declared in its containing scope: secretIngredient and chocolate.

When we call bake afterwards, it still remembers those two variables because there were stored in the closure.

A closure can refer to any variable or parameter in its scope. Check this out:


function cookiesBaker(cook){
    return function addSecretIngredient(secretIngredient){
        return function bakeCookie(chocolate){
            return `${cook} cooked a ${secretIngredient} ${chocolate} chocolate cookie.`
        }
    }
}

In this example, the inner function bakeCookie refers to a parameter from the outer cookiesBaker function (cook), a parameter from the outer addSecretIngredient function (secretIngredient) and a parameter from its own scope (chocolate).

const cook = cookiesBaker("Damien")

const secret = cook("peanut butter")

const result = secret("white")
// Damien cooked a peanut butter white chocolate cookie.

Here, we are taking one more step.

We return the inner function addSecretIngredient and store that in a variable. Then, we call that stored function, the result ( bakeCookie ) is stored inside another variable. Finally, we call that function. The final results, as you can see, remembers all the variables stored inside the closure.

We can also use this to make more general-purpose functions.

Let's say we want to create a function for all cookies baked by Johnny:

const bakedByJohnny = cookiesBaker("Johnny")

bakedByJohnny("coconut oil")("black") // Johnny cooked a coconut oil black chocolate cookie.

bakedByJohnny("")("milk") // Johnny cooked a  milk chocolate cookie.

Notice that instead of declaring a variable and storing the intermediate function inside it. I can call the inner function immediately because bakedByJohnny("coconut oil") is returning a function!

Ok, another little example. Let's create a function for all cookies baked by Sarah with peanut butter:

const bakedBySarahPeanutButter = cookiesBaker("Sarah")("peanut butter")

bakedBySarahPeanutButter("white")
//Sarah cooked a peanut butter white chocolate cookie.

bakedBySarahPeanutButter("black")
// Sarah cooked a peanut butter black chocolate cookie.

bakedBySarahPeanutButter("milk")
// Sarah cooked a peanut butter milk chocolate cookie.

Even though the two functions we created come from the same function definition, they are two distinct objects and both store different variables.

Note: The functions can be anonymous, like so:

let cookiesBaker = function(cook){
    return function(secretIngredient){
        return function(chocolate){
            return `${cook} cooked a ${secretIngredient} ${chocolate} chocolate cookie.`
        }
    }

This code would give the exact same results than before!

Fact Three: Closures can not only remember the variable in their containing scope, they can also update it.

Consider the following example:

const secretIngredient = function(){
    let ingredient = undefined
    return {
        changeIngredient: newIngredient => { ingredient = newIngredient },
        showIngredient: () => ingredient,
        type: () => typeof ingredient
    }
}

This function returns 3 closures. Each method in the object returned refer to a variable defined in the containing scope.

Now, let's prove that closures can not only read outer variables, they can also update them:

let i = secretIngredient()

i.showIngredient() // undefined
i.type() // undefined

i.changeIngredient("coconut oil")

i.showIngredient() // coconut oil
i.type() // string

Tadaaaaa!

Conclusion

Closures are one of those things that you most likely use very often. You probably didn't even know about it! Check your code and try to identify closures, get comfortable with them, and use their full powers!

Latest comments (8)

Collapse
 
nym02 profile image
Nayeem M. Muzahid

Can anybody explain this to me? cookiesBaker("Sarah")("peanut butter")

Collapse
 
saberhosneydev profile image
Saber Hosney

Given this is the definition of cookiesBaker function

let cookiesBaker = function(cook){
    return function(secretIngredient){
        return function(chocolate){
            return `${cook} cooked a ${secretIngredient} ${chocolate} chocolate cookie.`
        }
}
Enter fullscreen mode Exit fullscreen mode

As you can see here, there's 3 functions, one with two nested functions. each one of em takes a parameter. so we need to call the function with three different nested parameters in order to meet its requirement.
like so cookiesBaker("Sarah")("peanut butter")("white") which will return a value of "Sarah cooked a peanut butter white chocolate cookie."
How it worked?
cookiesBaker("Sarah") calling this alone will evaluate and set parameter "cook" equal to "Sarah" then returns a function. in which that function will be called as following cookiesBaker("Sarah")returnedFunction("peanut butter") which will evaluate parameter "secretgradient" to "peanut butter" then returns a function that will be called as cookiesBaker("Sarah")returnedFunction("peanut butter")returnedFunctionTwo("white") which will evaluate parameter "chocolate" to "white" then returns a string value equal to "Sarah cooked a peanut butter white chocolate cookie."

Collapse
 
yes_himanshu profile image
Himanshu

Very well explained with examples in following link
idontknowjavascript.com/2019/05/cl...

Collapse
 
sagar4u profile image
sagar4u

Closure is one of those things which causes my head spinning.
Nice article. Learnt a lot. Thank you.

Collapse
 
damcosset profile image
Damien Cosset

Glad you found it helpful :)

Collapse
 
moopet profile image
Ben Sinclair

If anyone does foo(bar)(baz) in code I have to maintain, I'm going to be cross.

Collapse
 
damcosset profile image
Damien Cosset

Yeah, I can understand that :D

Collapse
 
wizardrogue profile image
Joseph Angelo Barrozo

Learned a lot! Thanks!