Closures and It's Properties

Closures and It's Properties

With examples, let's discover closures properties and interesting use cases.

Closures allow a method to access variables of another method, even though the method left the stack. Closures are the combination of function and lexical scope.

To see closures in actions,

// A simple closures example
function hoc() {
  // "willBePreserved" is a closure
  const willBePreserved = "Screaming Eagle Cabernet Sauvignon";
  return function child() {
    console.log(willBePreserved);
  };
}

const returnedMethod = hoc();
returnedMethod();

Here, the hoc method has the property willBePreserved. This willBePreserved belongs to hoc and also used in the child method. When we are done calling the hoc method, the garbage collector came and remove the hoc from the calling stack. But, since the child method use the willBePreserved, it will be preserved. This willBePreserved is the closures.

Properties

A couple of properties of closures are,

  • Closures can be used with the event loop
  • Closures will not preserve a variable that is not referenced
  • Closures ignore the rules of hoisting

Closures can be used with the event loop

Let's see the closures in action with the event loop,

// An example of using Closures with WEB API
function closuresWithEventLoop () {
  const preservedVal = 'I am preserved!';
  setTimeout(() => {
    console.log(preservedVal); // display "I am preserved!"
  }, 5000);
}

Here the setTimeout will go to the web API to be executed. In the meantime the garbage collector will remove the closuresWithEventLoop method but will keep the preservedVal. After a delay of 5 seconds the preservedVal will be printed in the console.

Closures will not preserve a variable that is not referenced

Closures only preserve a variable from the garbage collector that is referenced.

// An example of using closures with WEB API
function closuresWithLocalVariable () {
  /**
  * Since not referenced in the returned method,
  * the "notPreservedVal" will be removed from garbage collector
  */
  const notPreservedVal = 'I am not preserved!';

  /**
  * Will be preserved from the garbage collector,
  * as it is used in the returned method
  */
  const preservedVal = 'I am preserved!';

  return function() {
     console.log(preservedVal); // display "I am preserved!"
  }
}

const returnedMethod = closuresWithLocalVariable();
returnedMethod(); // display "I am preserved!"

In this example, since the notPreservedVal is not referenced anywhere, it will not be preserved.

Closures ignore the Hoisting rule

JavaScript does not hoist the const or let. Hoisting is only available when declare a variable with var keyword. In the following example, we can see, even if the variable is declared with const, it will be displayed and printed.

// Example of closures ignores hoisting
function closuresHoising() {
  setTimeout(function() {
    console.log(willBePreserved);
  }, 1000);
  // "const" never hoisted, but here we can print it in "setTimeout" method
  const willBePreserved = "I am hoisted even declared with 'const'";
}

closuresHoising(); // Display "I am hoisted even declared with 'const'"

Interesting use case with Closures

With closures, we can come up with a couple of interesting coding use case,

  • Memory optimization
  • Memory encapsulation
  • Interesting pattern

Memory optimization

Let's consider a method, that returns the history of a country. To do so, it first load all the country, their history and keep them in a hashmap. Then according to the country name, it returns the history of the specific country.

// Use to fetch country list and their history
// Then return the history of a country passed as param
function getCountryHistory (countryName) {
  // Fetch all the country list from a public api
  // Sort them locally
  // Fetch history for each country
  // Load country history in a hashMap { [key: countryName]: countryHistory
  const countryHistory = {
    country_name_1: 'country_1_history',
    country_name_2: 'country_2_history',
    country_name_3: 'country_3_history',
  };

  return countryHistory[countryName];
}

getCountryHistory('country_name_1'); // Display 'country_1_history'

You might notice, every time we call the method getCountryHistory, it fetches all the data, store them in memory and return the result. With closures, we can optimize the solution, so only once the data will be fetched. Every time we call the method, the result will be showed from the closures. Let's see the optimized solution in action,

// optimize solution
// Use to fetch country list and their history
// Store them in a hashMap as closures
function loadCountryHistory () {
  // Fetch all the country list from a public api
  // Sort them locally
  // Fetch history for each country
  // Load country history in a hashMap { [key: countryName]: countryHistory
  const countryHistory = {
    country_name_1: 'country_1_history',
    country_name_2: 'country_2_history',
    country_name_3: 'country_3_history',
  };

  return function(countryName) {
     console.log(countryHistory[countryName]); 
  }
}

// this loads the country history in closures
const getCountryHistory = loadCountryHistory(); 

getCountryHistory('country_name_1'); // Display 'country_1_history'
getCountryHistory('country_name_2'); // Display 'country_2_history'

Interesting Pattern

Lets consider a function initialize() and it set some global value. The important thing is it should be called only one time.

let globalKey;

function initialize() {
  globalKey = "value";
  console.log("Global key is set");
}

initialize();
initialize();
initialize();

Here the function is being invoked 3 times. With closure, we need to call it once.

let globalKey;

function initialize() {
  let isCalled = false;
  return () => {
    if (isCalled) {
      return;
    }
    isCalled = true;
    globalKey = "value";
    console.log("Global key is set");
  };
}

const startOnce = initialize();
startOnce();
startOnce();
startOnce();

Now, this will be only called once.

Another approach to solve the problem can be, updating the function reference after being initialize at the first time.

let globalKey;

function initialize() {
  globalKey = "value";
  console.log("Global key is set");
  initialize = () => {
    console.log("Aboarting! already set the value");
  };
}

initialize();
initialize();

Using IIFE, we can do some improvement,

let globalKey;

const initialize = (() => {
  let called = 0;
  return () => {
    if (called) {
      console.log("Already Set");
      return;
    }
    called = 1;
    globalKey = "Global Value";
    console.log("Set the value");
  };
})();

initialize();
initialize();
initialize();

We will see the output of

Set the value
Already Set
Already Set

Another Interesting Pattern [Bonus]

Let's consider following snippet,

const array = [1, 2, 3, 4];

function traverse() {
  for (var i = 0; i < array.length; i++) {
    setTimeout(function () {
      console.log(i);
    }, 1000);
  }
}

traverse();

The output will be,

4 4 4 4

In this case, the var has used the hoisting and got the latest index. using let instead of var can resolve the issue.

const array = [1, 2, 3, 4];

function traverse() {
  for (let i = 0; i < array.length; i++) {
    setTimeout(function () {
      console.log(i);
    }, 1000);
  }
}

traverse();

Since we are in the world of closures, let's solve it using IIFE and closures,

const array = [1, 2, 3, 4];

function traverse() {
  for (var i = 0; i < array.length; i++) {
    (function (val) {
      setTimeout(function () {
        console.log(val);
      }, 1000);
    })(i);
  }
}

traverse();

Our desired output now,

0 1 2 3

Q/A

Final Thoughts

Closures are very interesting concepts and powerful tools. Please let me know if your thoughts/usage of closures in daily programming.

References: JavaScript: The Advanced Concepts by Andrei Neagoie