Closure

in JavaScript

Preface

  1. Closure is complex, but powerful.
  2. Academically, it is part of lambda calculus.
  3. Practically, it is used in almost all JavaScript apps/packages and is a central concept for the language

Today we are going to focus on a practical definition and applications for closure.

Background / Refresher

  1. Scope
  2. First Class & Higher Order Functions
  3. Call Stack & Execution Context

To talk about closure we first have to have a solid grasp of three important JS concepts:

If you understand these three things well, you will also understand closure.

Scope

In JavaScript, as in virtually every other programming language, we have lexical scope, meaning variables are automatically attached to the scope in which they are originally declared.

I. Lexical Scope

Note: JavaScript also utilizes dynamic scope via the "this" keyword and bindings, but that is a an advanced topic for another time.

var foo = 'bar';

// ... 10 thousand lines later ...

console.log(foo);  // bar

Scope

I. Lexical Scope

  • Scopes can be nested within each other predictably.
  • Children have access to items in their parents' scopes.
  • Parents do not have access to items in their children's scopes.
// global scope
parentFunction() {




}
childFunction() {

 

}

Scope

II. Functional Scope

In JavaScript, we use functions to create new levels of scope.

Note: You might hear some say that JavaScript only has functional + global scope, but technically since EcmaScript 2015 (ES6) we have block scope as well using the let keyword.

var foo = 'one';

function newScope() {
  var foo = 'two';
  console.log(foo);
}

console.log(foo);  // one
newScope(); // two

var one = 'I am in the global scope.';

function outerFunction() {

  console.log(one);
  var two = 'I am in the outer function\'s scope';

  innerFunction();  // note this function is "hoisted"
  
  function innerFunction() {
     console.log(two);
     console.log('I am in the inner function\'s scope');
  }

}

console.log(one); // I am in the global scope.
console.log(two); // undefined

outerFunction();
/*
  I am in the global scope.
  I am in the outer function's scope.
  I am in the inner function's scope.
*/

innerFunction(); // throws ReferenceError: innerFunction is not defined

Scope

Takeaways

  1. Default scope is lexical, meaning variables live where they were declared
  2. Scopes can be nested & can access parents but not children
  3. Use functions to create different scopes

First Class & Higher Order Functions

  • JavaScript has First Class Functions, meaning that functions can be treated just like other data types
  • Example 1: Functions can be passed as arguments to other functions
function myFirstFunc(anotherFunc) {
    return anotherFunc();
}

function mySecondFunc() {
    console.log('hello foobar');
}

myFirstFunc(mySecondFunc); // hello foobar

I. First Class Functions

First Class & Higher Order Functions

  • Example 2: Functions can be assigned to variables
var myFunction = function() { console.log('hello foobar') };

myFunction();  // hello foobar

I. First Class Functions

First Class & Higher Order Functions

  • Higher Order Functions are more of a mathematical concept rather than specific to programming languages.
  • A HOF is a function that does at least one of the following (or both):
    • Accepts a function as a parameter
    • Returns a function as a result

II. Higher Order Functions

First Class & Higher Order Functions

Example


/* this is called an identity function */

function myHOF(myFunc) {
  return myFunc;
}

function test() {
  alert('test');
}

var myIdentityFunction = myHOF(test);
myIdentityFunction(); // test

II. Higher Order Functions

First Class & Higher Order Functions

Takeaways

  1. First Class Functions programmatically behave like other types
  2. Higher Order Functions take a function, return a function, or both

Call Stack & Execution Context

  • When JavaScript executes a function, it creates a call object that gets pushed on top the call stack
  • The call stack is a stack data structure. You put the most recently-executed function on top of the stack

I. Call Stack

myFunc

()

{ myFunc }

function myFunc(callback) {
  return callback();
}

function myCallBack() {
  console.log('foo');
}

myFunc(myCallBack);

Call Stack & Execution Context

  • When another function gets called within the first one (such as a callback), its call object gets placed on top of the current function in the call stack

I. Call Stack

myCallback

()

{ myFunc }

{ myCallback }

function myFunc(callback) {
  return callback();
}

function myCallBack() {
  console.log('foo');
}

myFunc(myCallBack);

Call Stack & Execution Context

  • As functions return, their call objects get popped off the top of the call stack, and the next item down resumes execution until the stack is empty

I. Call Stack

{ myFunc }

{ myCallback }

{ myFunc }

return
return

Call Stack & Execution Context

  • Each call object carries a reference to its scope as well as its parents' scopes via its execution context.

II. Execution Context

{ myFunc }

{ myCallback }

  • In our first example, myFunc's execution context references the global object, and myCallback's execution context references myFunc
function myFunc(callback) {
  return callback();
}

function myCallBack() {
  console.log('foo');
}

myFunc(myCallBack);

Takeaways

  1. Executing functions creates respective call objects that get placed on a stack with the most recent function call on top.
  2. Every call object maintains execution context on the stack with references to its parents

Call Stack & Execution Context

Closure

Closure is when a function "remembers" its lexical scope even when it is executed elsewhere.

Closure

We say the original scoped variables are "closed over."

function closureCounter() {

  var count = 0;

  return function innerFunction() {
    return ++count;
  };

}

Note: innerFunction must reference the count variable of its parent for closure to occur.

Closure



/**
 * A Higher Order Function w/closure
 */
function closureCounter() {
  
    var count = 0;
  
    return function innerFunction() {
      return ++count;
    };
  
  }
  
  var myCounter = closureCounter();

{ closureCounter }

Call Stack

Memory Heap

Closure



/**
 * A Higher Order Function w/closure
 */
function closureCounter() {
  
    var count = 0;
  
    return function innerFunction() {
      return ++count;
    };
  
  }
  
  var myCounter = closureCounter(); 

{ count }

Call Stack

Memory Heap

Closure



/**
 * A Higher Order Function w/closure
 */
function closureCounter() {
  
    var count = 0;
  
    return function innerFunction() {
      return ++count;
    };
  
  }
  
  var myCounter = closureCounter(); 
  myCounter(); // 1

{ count }

{ innerFunction }

Call Stack

Memory Heap

Closure



/**
 * A Higher Order Function w/closure
 */
function closureCounter() {
  
    var count = 0;
  
    return function innerFunction() {
      return ++count;
    };
  
  }
  
  var myCounter = closureCounter();
  myCounter(); // 1
  myCounter(); // 2

{ count }

{ innerFunction }

Call Stack

Memory Heap

Closure



/**
 * A Higher Order Function w/closure
 */
function closureCounter() {
  
    var count = 0;
  
    return function innerFunction() {
      return ++count;
    };
  
  }
  
  var myCounter = closureCounter();
  myCounter(); // 1
  myCounter(); // 2

  var mySecondCounter = closureCounter();

{ count }

Call Stack

Memory Heap

{ closureCounter }

Closure



/**
 * A Higher Order Function w/closure
 */
function closureCounter() {
  
    var count = 0;
  
    return function innerFunction() {
      return ++count;
    };
  
  }
  
  var myCounter = closureCounter();
  myCounter(); // 1
  myCounter(); // 2

  var mySecondCounter = closureCounter();
  mySecondCounter(); // 1

{ count }

Call Stack

Memory Heap

{ count }

{ innerFunction }

Closure



/**
 * A Higher Order Function w/closure
 */
function closureCounter() {
  
    var count = 0;
  
    return function innerFunction() {
      return ++count;
    };
  
  }
  
  var myCounter = closureCounter();
  myCounter(); // 1
  myCounter(); // 2

  var mySecondCounter = closureCounter();
  mySecondCounter(); // 1

  myCounter(); // 3

{ count }

Call Stack

Memory Heap

{ count }

{ innerFunction }

Job Interview Closure

Here is a common example of a trick question that has been historically asked in many job interviews

var obj = {};

for (var i = 1; i <= 3; i++) {
  obj[i] = function() { console.log(i); };
}

/* what is the answer for the following? */
obj[1]();
obj[2]();
obj[3]();

Job Interview Closure

All keys in the object are functions that print "4". Why?

var obj = {};

for (var i = 1; i <= 3; i++) {
  obj[i] = function() { console.log(i); };
}

/* what is the answer for the following? */
obj[1](); // 4
obj[2](); // 4
obj[3](); // 4

Job Interview Closure

var obj = {};

for (var i = 1; i <= 3; i++) {
  obj[i] = function() { console.log(i); };
}

/* what is the answer for the following? */
obj[1](); // 4
obj[2](); // 4
obj[3](); // 4

Answer: i is on the global scope, and each console.log references the same i, which is terminally set to 4 after the loop.

Job Interview Closure

How can you solve this using closure?

var obj = {};

for (var i = 1; i <= 3; i++) {
  obj[i] = function() { console.log(i); };
}

/* what is the answer for the following? */
obj[1](); // 4
obj[2](); // 4
obj[3](); // 4

Job Interview Closure

For every iteration of the loop, close around i with a function.

var obj = {};

for (var i = 1; i <= 3; i++) {
  (function(j) { 
     return obj[j] = function() { console.log(j); };
   })(i);
}

obj[1](); // 1
obj[2](); // 2
obj[3](); // 3

Note: This function is an IIFE (Immediately Invoked Function Expression)

Practical Applications of Closure

I. Module Pattern

One of the two main patterns in JavaScript programming is the module pattern, which groups programs into functions called modules.

var myModule = (function(){
  var privateMember = { foo: 'bar' };
  return {
    publicMethod: function() {
      console.log(privateMember.foo);
    }
  };
})();

myModule.publicMethod(); // "bar"

Practical Applications of Closure

I. Module Pattern

In general:

  1. There must be an outer enclosing function that runs at least once
  2. There must be a function in the returned object that references closed over variables
var myModule = (function(){
  var privateMember = { foo: 'bar' };
  return {
    publicMethod: function() {
      console.log(privateMember.foo);
    }
  };
}());

myModule.publicMethod(); // "bar"

Practical Applications of Closure

I. Module Pattern

What benefits does this give us?

  1. We gain encapsulation - the ability to restrict access to private stuff
  2. Flexible namespace, for example jQuery is a module aliased as $.
var myModule = (function(){
  var privateMember = { foo: 'bar' };
  return {
    publicMethod: function() {
      console.log(privateMember.foo);
    }
  };
}());

myModule.publicMethod(); // "bar"

Practical Applications of Closure

II. Functional Programming Methods

function addSalesTax(taxRate, value) {
  return (value + (taxRate / 100) * value);
}

/* how to add California sales tax */

addSalesTax(7.25, 100); // 107.25
addSalesTax(7.25, 50); // 53.625
addSalesTax(7.25, 25); // 26.8125

Consider this function to add sales tax:

Practical Applications of Closure

II. Functional Programming Methods

function addSalesTax(percentage) {
  var tax = percentage / 100;
  
  return function(value) {
    return value + (value * tax);
  }
}

/* add sales tax for California */

var addCaliforniaSalesTax = addSalesTax(7.25);
addCaliforniaSalesTax(100); // 107.25
addCaliforniaSalesTax(50); // 53.625
addCaliforniaSalesTax(25); // 26.8125

With closure, we can make our API more composable and less clunky.

How to Build Closures

in General

  1. Start with an outer (parent) function
  2. Add variables scoped within the outer function
  3. Return an inner (child) function w/references to parent variables
  4. Assign an external variable to the invocation (return value) of the outer function. This variable holds the closure state.
function outer() {  // step 1
  var foo = 0; // step 2
  return function inner() {  // step 3
    return ++foo;  // step 4
  }
}

var myFoo = outer(); // step 5

Summary

JavaScript supports:

 

  • Lexical Scope
  • First Class and Higher Order Functions
  • Persistent execution context

 

Therefore JavaScript supports closures, which are references to functional scope outside of where they were  originally declared.

Closure

By Michael Hueter

Closure

A Complete Introduction to Closure in JavaScript

  • 858