DEV Community

Mohammed Ali
Mohammed Ali

Posted on

Demystifying Closures in JS

  • Needs to be mastered to understand the intricate details of the language.
  • Not created like we create an array or function.
  • A fn returning another fn which is stored inside the LHS variable.
const secureBooking = function(){
  let passengerCount = 0;
  return function(){
    passengerCount++;
    console.log(`${passengerCount} passengers`);
  }
}

const booker = secureBooking();

booker();
booker();
booker();
Enter fullscreen mode Exit fullscreen mode
  • secureBooking fn is executed in global scope.
  • A new execution context will be created on the global execution context.
  • Every execution context(EC) has its own variable environment with all of its variables. 'let passengerCount = 0' is defined inside this EC. It can access all varibles of its parent scope.
  • A new fn is returned, and will be stored in booker.
  • The Global EC will also contain the booker variable.
  • Now secureBooking() will be removed from EC and is gone. The secureBooking EC environment is no longer active. But still the passengerCount variable is being accessed which was defined at the time of fn creation.
  • Still, inner fn will be able to access the passengerCount varible defined inside outer fn. This happens due to closure.
  • Closure makes the fn remember the varibles defined surrounding the birthplace of the fn or its EC.
  • booker is not a fn, located in global scope.
  • Now executing booker(); for the first time will create an EC on the call stack, with its own variables.
  • A fn has access to the variable environment of the execution context in which the fn was created i. secureBooking. Hence, booker will have access to variables defined in variable environment of secureBooking fn. This connection of fn's birthplace i.e of definition to its surrounding variable environment is called Closure even after the EC of secureBooking fn containing the fn has popped out of Call Stack.
  • Closure: Variable Environment attached to the fn, exactly as it was at the time and place the fn was created.
  • Scope chain is preserved through the closure, even though EC is gone the variable enviroment keeps living somehow in the Engine. Hence, we say booker fn is closed over parent fn including parent fn arguments which we don't have here.
  • Using closure, a fn does not lose connection to the variables defined surrounding its birthplace.
  • If variable is not in current scope, JS looks into closure even before looking up the scope chain. Suppose if there is a global variable, even then the variable defined in its closure is looked up first.
  • Closure has priority over scope chain.
  • After running the booker() for the first time, value of passengerCount is incremented, logged in console and then booker is popped off call stack.
  • Execution moves to next line, a new EC is created but the closure variable is still there. The existing value is incremented and the EC is popped out.
  • Again the third time this process repeats.

SUMMARY

  • Defn: A closure is closed-over variable environment of the EC in which a fn was created, even after that EC has gone.

  • Also, a closure gives a fn access to all the variables of its parent fn, even after that parent fn has returned. The fn keeps a reference to its outer scope, which preserves the scope chain throughout time.

  • A closure ensures that the fn doesn't lose connection to variables that existed at the time of fn's birth. Its like a backpack that a fn carries around wherever it goes. This backpack has all the variables that were present in the environment where the fn was created.

  • We don't have to create closures manually. Also, we can't even access closed-over variables explicitly. A closure is not a tangible JS object i.e we cannot reach to a closure and take variables from it. Its an internal property of a fn. To take a look at backpack, "console.dir(booker);"
    [[Scope]] will show you about the VE of this fn call.

  • [[]] means its an internal property, which we cannot access from our code.

  • We always don't need to return a fn from another fn to create a closure. In below example, variable 'f' wasn't even defined inside the fn also, as its in global scope. Its able to access the 'a' variable also even after g() has finished its EC. 'a' is inside the backpack of 'f' now.

let f;

const g = function(){
  const a = 23;
  f = function() {
    console.log(a*2); // 46
  };
};


const h = function(){
  const b = 777;
  f = function(){
    console.log(b*2); // 1554
  };
};

g();
f();
console.dir(f);

// f fn is reassigned using h fn. Hence, old closure value i.e 'a' will be replaced with new value 'b' which can be verified using console.dir().
h();
f();
console.dir(f);
Enter fullscreen mode Exit fullscreen mode
  • setTimeout(callbackFnToBeCalled, Delay);
  • Closure variables have higher priority over scope chain.
// Boarding Passengers using Closures
const boardPassengers = function(n, wait){
  const perGroup = n / 3;

  setTimeout(function(){
    console.log(`We are now boarding all ${n} passengers`);
    console.log(`There are 3 groups, each with ${perGroup} passengers`)
  }, wait*1000);

  console.log(`Will start boarding in ${wait} seconds`);
}

boardPassengers(180, 3);
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
jonrandy profile image
Jon Randy ๐ŸŽ–๏ธ • Edited

Nesting functions is not required