DEV Community

Mavis
Mavis

Posted on

Closures In JavaScript

Definition 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 you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

Playground

const sayHi = () => {
    const name = 'Nic'
    function sayIt(){console.log(`Hi, ${name}`)}
    return sayIt
}
const sayWhat = sayHi()
sayWhat() 
Enter fullscreen mode Exit fullscreen mode

In above case, what happen when call sayWhat(), the result is Hi, Nic.
If a function return something which have some references to this function scope variables, then the variables will be moved to closure box instead of collecting by garbage.
As sayHi contain sayIt, and sayIt is executed when sayWhat was called. Then the variable name in the sayHi will be removed to closure box as it's used in sayIt. when sayIt is executed, it will look for the name in its scope first, if cannot find it, then go to outer closure box to search. That's how closure works.

const sayHi = () => {
    setTimeout(() => {console.log(name)}, 4000)
    const name = 'Oodie'
}
sayHi() 
Enter fullscreen mode Exit fullscreen mode

In above case, what happen when call sayHi(), the result is Oodie. The explanation is the same as above.

What's the benefit from closures

Memory Efficient

In below case, every time we call heavyDuty function, we need to create a memory to store bigArray, as we call it many times, so it's kind of waste our memory. You may say, we can create big array outside of heavyDuty function. But that will pollute the global scope. That's why we apply closure here.

function heavyDuty(index){
    const bigArray = new Array(10000).fill('I am a big array')
    console.log('created!')
    return bigArray[index]
}
heavyDuty(8888) // created
heavyDuty(8888) // created
heavyDuty(8888) // created
Enter fullscreen mode Exit fullscreen mode

Let apply closure to above case. We only need to create big array one time. And we can use it anywhere and anytime.

function heavyDutyWithClosure(){
    const bigArray = new Array(10000).fill('silly')
    console.log('created!')
    return function(index){
            // bigArray will always refer to outside bigArray
            return bigArray[index]
        }
}
const getHeavyDuty = heavyDutyWithClosure()
getHeavyDuty(8888)() // created
getHeavyDuty(8888)()
getHeavyDuty(8888)() 
Enter fullscreen mode Exit fullscreen mode

Encapsulation

const makeNuclearButton = () => {
    let timeWithoutDesctruction = 0;
    const passTime = () => timeWithoutDesctruction++;
    const totalPeaceTime = () => timeWithoutDesctruction++;
    const launch = () => {
        timeWithoutDesctruction = -1;
        return "boom";
    };
    setInterval(passTime, 1000);

    return {
        launch: launch,
        totalPeaceTime: totalPeaceTime,
    };
};
const ohno = makeNuclearButton();
console.log(ohno.totalPeaceTime()); // 0
// after 10 second, execute this
console.log(ohno.totalPeaceTime()); // 10
// after 2 second, execute this
console.log(ohno.totalPeaceTime()); // 12, as setInterval is working
Enter fullscreen mode Exit fullscreen mode

In above case, you can not change timeWithoutDesctruction, but can get it.
what if we don't want you to get launch function? just remove it from return object.

Exercise

Try to convert them by using closures.
Q1

let view
function initialize(){
    view = 'boom'
    console.log('view has been set!')
}
initialize() // view has been set!
initialize() // view has been set!
initialize() // view has been set!
Enter fullscreen mode Exit fullscreen mode

Result

let view
function initialize(){
    let called = 0
    return function(){
        if(called > 0){
            return;
        }else{
            view = 'boom'
            console.log('view has been set!')
            called++
        }
    }

}
const startOnce = initialize() 
startOnce() // view has been set!
startOnce()
startOnce()
Enter fullscreen mode Exit fullscreen mode

Q2

const array = [1,2,3,4]
for(var i=0; i<array.length; i++){
    setTimeout(function(){console.log('I am at index ' + i)}, 3000)
}
// I am at index 4
// I am at index 4
// I am at index 4
// I am at index 4, as var do not have block scope
Enter fullscreen mode Exit fullscreen mode

Result

// easy way to fix them by keyword let
const array = [1,2,3,4]
for(let i=0; i<array.length; i++){
    setTimeout(function(){console.log('I am at index ' + i)}, 3000)
}
// I am at index 0
// I am at index 1
// I am at index 2
// I am at index 3
Enter fullscreen mode Exit fullscreen mode
// easy way to fix them by keyword let
const array = [1,2,3,4]
for(var i=0; i<array.length; i++){
    setTimeout(function(){console.log('I am at index ' + i)}, 3000)
}
// I am at index 0
// I am at index 1
// I am at index 2
// I am at index 3
Enter fullscreen mode Exit fullscreen mode
// let fix it by using IIFE
const array = [1,2,3,4]
for(var i=0; i<array.length; i++){
  (function(closureI){setTimeout(function() {console.log(closureI)})})(i)
}
// I am at index 0
// I am at index 1
// I am at index 2
// I am at index 3
Enter fullscreen mode Exit fullscreen mode

Learn more about the IIFE

Top comments (0)