JavaScript Closures

A closure is the combination of a function and the lexical environment within which that function was declared.

This article stands on the shoulder of the respectful by largely referencing to MDN - JavaScript Closures and is composed with my own understanding and explanation.

JavaScript closures is somewhat alternative to OOP objects which allows for associating properties with methods.

A closure combined with IIFE or wrapper factory function is somewhat alternative to OOP namespace which provides a separate environment from global scope.

Lexical Scoping

lexical scoping uses the location where a variable is declared within the source code to determine where that variable is available. Nested functions have access to variables declared in their outer scope.

Closure

Functions in JavaScript form closures. A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created.

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

In essence, makeAdder is a function factory; add5 and add10 are both closures while store different lexical environments(of one x is 5 and of the other x is 10).

Practical closures

Possibly as event handler or callback - a single function which is executed in response to the event.

Emulating private methods with closures

Private methods also provide a powerful way of managing global namespace, keeping non-essential methods from cluttering up the public interface to your code.

// module pattern example
var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var counter1 = makeCounter();
var counter2 = makeCounter();
alert(counter1.value()); /* Alerts 0 */
counter1.increment();
counter1.increment();
alert(counter1.value()); /* Alerts 2 */
counter1.decrement();
alert(counter1.value()); /* Alerts 1 */
alert(counter2.value()); /* Alerts 0 */

In about example, counter1 and conter2 are just like 2 instance of same class in OOP.

Closure Scope Chain

For every closure there are 3 scopes:

A common mistake: Creating closures in loops

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp();

The value of item.help (and preceding item and i) is determined when the onfocus callbacks are executed.

One solution is to use another factory closure outside which create a new lexical environment for each callback(kind of freeze item.help earlier than onfocus callbacks execution) as follows.

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function makeHelpCallback(help) {
  return function() {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}

setupHelp();

Another solution is to freeze the i value by IIFE as follows.

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    (function() {
       var item = helpText[i];
       document.getElementById(item.id).onfocus = function() {
         showHelp(item.help);
       }
    })(); // Immediate event listener attachment with the current value of item (preserved until iteration).
  }
}

setupHelp();

And this issue can also be gracefully solved with ES2015 block scope keyword let which freeze the i value or item value to exactly loop value.

And the callback method of .forEach of JavaScript Array also provide one solution by freezing array item in new lexical environment.

Performance consideration

It is unwise to unnecessarily create functions within other functions if closures are not needed for a particular task, as it will negatively affect script performance both in terms of processing speed and memory consumption.

For instance, when creating a new object/class, methods should normally be associated to the object's prototype rather than defined into the object constructor. Since when attached to constructor, the methods would get reassigned for every object creation.

Instead of writing:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

Writing the following instead:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}

MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

And even better when writing (which reserves the original prototype by attaching instead of overwrite):

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}

MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

References


* cached version, generated at 2018-12-06 05:26:10 UTC.

Subscribe by RSS