Javascript Closures Long Live Locals

JavaScript Closures – Long Live Locals

A hot air balloon forms closure to hold hot air.

Googling ‘closure’ gives you this TLDR version:

In programming languages, a closure (also lexical closure or function closure) is a function or reference to a function together with a referencing environment — a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.


More often, it is also DFDC version (Didn’t Follow, Don’t Care) for folks new or not sure about Closures.

Closure Simplified:

Closure is formed when a function can remember and access its lexical scope even when it’s invoked outside of its lexical scope.

This is the fifth post of our blogger month at Xebia. Every day one of the Xebian post a new blog. You can follow the full series at http://blog.xebia.in/pages/blogger-month-2015/.

Didn’t follow? Don’t worry. Many developers (include me in this list) don’t get it first time. Check this example:

function outerFunction() {
    var outerVariable = 'I am local to outerFunction';

    function innerFunction() {     //This is a closure. Simple.
       console.log(outerVariable); //It can access outer scope.
    }

    innerFunction();
}

outerFunction();

JavaScript function gives rise to new scope. Nested function, innerFunction in above example, can access variables defined in its outer scope, like hot air balloon can access outside air. This can be nested to multiple levels forming spheres of scopes. The innermost scope has access to all its outer scopes.

Note that innerFunction is called from within its lexical scope which is outerFunction in this case. We will soon see how to call it outside of its lexical scope by returning reference to innerFunction from outerFunction.

What is this lexical scope now?

It’s called lexical because of the way the scope of a variable is defined by its location within the source code. The variable’s scope is apparent lexically. Thus when we nest function within function (to form a parachute, or a transformer, or a dream within dream), each having scope of its own, the local variables are lexically scoped in these spheres of scopes.

Globals are Immortal.

Global variables live the entire life span of the application and are not garbage collected (GC’ed). But due to shared namespace in JavaScript (window object in browser environment), we know the quirks of using globals and often resort to locals.

Locals are better but they die.

What a world it would be with all locals when they die as soon as the function holding them completes? Hot air is gone when balloon deflates. Locals are GC’ed once the function holding them completes. Check below example:

function outerFunction() {
    var outerVariable = 'I am outside';

    function innerFunction() {
       console.log(outerVariable);
    }

    innerFunction();
}

outerFunction();            //Logs 'I am local to outerFunction'
console.log(outerVariable); //Logs outerVariable is not defined

How Locals can Live Longer?

If we return reference of innerFunction from outerFunction, outerVariable won’t get GC’ed and can live longer.

function outerFunction() {
    var outerVariable = 'I am local to outerFunction';

    return function innerFunction() {
       console.log(outerVariable);
    };
}

var innerFunctionReference = outerFunction();

//Calling innerFunction outside of its lexical scope:
innerFunctionReference(); //Logs 'I am local to outerFunction'

Inner function can hold onto variables defined in its outer scope as a reference to it is still alive. Mr. Garbage Collector lets them live longer.

Inner function can remember not just the variables defined in outer scope but also the arguments passed to its outer function. Run this and see it for yourself:

var messages = ['I', 'will', 'be', 'back'];

//Without closure:
for(var i = 0; i < messages.length; i++) {
    setTimeout(function () {
        console.log(messages[i]); //Logs ???
    }, 1000);
}

//With closure:
for(var i = 0; i < messages.length; i++) {
    (function (i) {                   //Closure holding onto i
        setTimeout(function () {
            console.log(messages[i]); //Logs the popular message
        }, 1000);
    })(i);
}

Without closure, it logs undefined 4 times as the anonymous function passed to setTimeout doesn’t remember what was ‘i’ at the time it was set to timeout. It runs when i has reached 4 which is undefined in messages array.

With closure it works as expected because now it can hold onto value of variable ‘i’ in each iteration. Value of ‘i’ is locked into inner function (inner balloon) in the closure of outer function (outer balloon) we wrapped it into.

Outer balloon forming closure to hold onto its inner balloon while looping

Why do we need Closure?

Why use closure when we can live without it? But what a world it would be to remind (pass arguments to) a function when it can remember things around it? Think about it. It can open doors to do lot more than what we have been doing right now.

Benefits/Practical uses of Closure:

  • Create self sustained and persistent modules with public/private properties and methods.
  • Make semi-computations or currying to hold onto intermediate results to arrive at final/other results later. See My First Closure below.
  • Remembering/caching what happened last time that can be used in AI, Machine Learning, Parsers, etc.
  • Hold onto index while looping some asynchronous code.

My First Closure:

Say you want to create a Heatmap wherein days are columns and rows are stocks and each cell holds the stock price with color to indicate price fluctuation between high and low of that stock in that period. And say you want to do this for last nDays (nDays is user input) for 30 stocks.

That will be nDays X 30 computations to decide the color of the cell. What about min and max computation in this period as that needs to be calculated based on prices in that period? Would you do this minMax calculation nDays X 30 times. Nah! I used closure to make cellColor remember what min and max is for all the stocks:

var nDays = 5;

//Create closure to remember min and max prices
var cellColor = (function (n) {
  var colors = ['R', 'O', 'Y', 'L', 'G'];
  var stocks = getMyStocks(n);
  var minPrices = getMinPrices(stocks);
  var maxPrices = getMaxPrices(stocks);

  return function (stockIndex, day) {
    var colorIndex =
      Math.round((stocks[stockIndex].prices[day] - minPrices[stockIndex]) /
        (maxPrices[stockIndex] - minPrices[stockIndex]) *
        (colors.length - 1));
      return colors[colorIndex];
    };
})(nDays);

//Generate Heatmap
console.log('MON|TUE|WED|THU|FRI|');
for(var stock = 0; stock < stocks.length; stock++) {
  var heatMapRow = '';
  for(var day = 0; day < nDays; day++) {
    heatMapRow += stocks[stock].prices[day] + '-' + cellColor(stock, day) + '|';
  }
  console.log(heatMapRow);
}

//Some random stocks I never bought ;-)
function getMyStocks(n) {
  var stocks = [];
  for(var i = 0; i < 30; i++) {
    var stock = {
      symbol: 'STOCK' + i,
      prices: []
    };
    for(var day = 0; day < n; day++) {
      stock.prices.push(parseInt(Math.random() * 10));
    }
    stocks.push(stock);
  }
  return stocks;
}

function getMinPrices(stocks) {
  var minPrices = [];
  stocks.forEach(function (stock) {
    minPrices.push(Math.min.apply(null, stock.prices));
  });
  return minPrices;
}

function getMaxPrices(stocks) {
  var maxPrices = [];
  stocks.forEach(function (stock) {
    maxPrices.push(Math.max.apply(null, stock.prices));
  });
  return maxPrices;
}

/* RESULT:
MON|TUE|WED|THU|FRI|
6-G|5-L|1-O|3-Y|0-R|
9-G|1-R|9-G|3-O|6-L|
1-R|2-O|1-R|9-G|5-Y|
1-R|7-L|2-O|8-G|6-L|
5-Y|3-O|1-R|1-R|9-G|
7-L|5-Y|2-R|5-Y|9-G|
2-Y|0-R|4-G|2-Y|2-Y|
2-O|2-O|0-R|9-G|0-R|
0-R|3-Y|1-O|2-O|6-G|
1-R|8-G|3-O|4-Y|5-Y|
4-L|4-L|5-G|5-G|2-R|
4-Y|3-O|8-G|5-Y|1-R|
9-G|7-L|1-R|9-G|2-O|
7-G|4-Y|6-L|5-L|1-R|
0-R|4-G|4-G|3-L|1-O|
3-R|5-Y|4-O|5-Y|8-G|
7-G|2-O|0-R|2-O|7-G|
6-L|2-O|0-R|5-L|7-G|
7-G|5-L|5-L|1-R|6-L|
1-O|1-O|4-Y|8-G|0-R|
8-G|8-G|8-G|6-L|0-R|
0-R|0-R|5-G|5-G|2-Y|
2-R|9-G|5-Y|7-L|5-Y|
3-O|1-R|5-Y|7-L|9-G|
0-R|3-Y|0-R|0-R|8-G|
8-L|3-R|5-O|9-G|9-G|
8-G|8-G|5-Y|7-L|1-R|
5-L|2-O|8-G|8-G|0-R|
4-Y|5-G|3-R|4-Y|4-Y|
9-G|4-O|3-R|6-Y|9-G|
*/

Care for Closure. Get closer to Closure 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *