Michael Hueter
Instructor at Rithm School and Full-Stack Software Developer
Today we are going to focus on a practical definition and applications for closure.
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.
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.
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
// global scope
parentFunction() {
}
childFunction() {
}
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
function myFirstFunc(anotherFunc) {
return anotherFunc();
}
function mySecondFunc() {
console.log('hello foobar');
}
myFirstFunc(mySecondFunc); // hello foobar
var myFunction = function() { console.log('hello foobar') };
myFunction(); // hello foobar
Example
/* this is called an identity function */
function myHOF(myFunc) {
return myFunc;
}
function test() {
alert('test');
}
var myIdentityFunction = myHOF(test);
myIdentityFunction(); // test
myFunc
()
{ myFunc }
function myFunc(callback) {
return callback();
}
function myCallBack() {
console.log('foo');
}
myFunc(myCallBack);
myCallback
()
{ myFunc }
{ myCallback }
function myFunc(callback) {
return callback();
}
function myCallBack() {
console.log('foo');
}
myFunc(myCallBack);
{ myFunc }
{ myCallback }
{ myFunc }
return
return
Each call object carries a reference to its scope as well as its parents' scopes via its execution context.
{ myFunc }
{ myCallback }
function myFunc(callback) {
return callback();
}
function myCallBack() {
console.log('foo');
}
myFunc(myCallBack);
Closure is when a function "remembers" its lexical scope even when it is executed elsewhere.
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.
/**
* A Higher Order Function w/closure
*/
function closureCounter() {
var count = 0;
return function innerFunction() {
return ++count;
};
}
var myCounter = closureCounter();
{ closureCounter }
Call Stack
Memory Heap
/**
* A Higher Order Function w/closure
*/
function closureCounter() {
var count = 0;
return function innerFunction() {
return ++count;
};
}
var myCounter = closureCounter();
{ count }
Call Stack
Memory Heap
/**
* 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
/**
* 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
/**
* 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 }
/**
* 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 }
/**
* 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 }
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]();
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
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.
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
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)
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"
I. Module Pattern
In general:
var myModule = (function(){
var privateMember = { foo: 'bar' };
return {
publicMethod: function() {
console.log(privateMember.foo);
}
};
}());
myModule.publicMethod(); // "bar"
I. Module Pattern
What benefits does this give us?
var myModule = (function(){
var privateMember = { foo: 'bar' };
return {
publicMethod: function() {
console.log(privateMember.foo);
}
};
}());
myModule.publicMethod(); // "bar"
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:
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.
function outer() { // step 1
var foo = 0; // step 2
return function inner() { // step 3
return ++foo; // step 4
}
}
var myFoo = outer(); // step 5
JavaScript supports:
Therefore JavaScript supports closures, which are references to functional scope outside of where they were originally declared.
By Michael Hueter
A Complete Introduction to Closure in JavaScript