12. Closures in JavaScript
In our last post we understood the execution context completely by understanding the this
variable and the ways it gets its value and how we can set its value explicitly.
At this point, we are equipped enough to dive into the feature of closures.
Closures
A Closure is said to be created when an inner function which is inside an outer function is made available outside the outer function and its able to access the execution context of the outer function.
Let's see an example: Example 1:
function outer(outerParam){
var outerVar = "hello";
return function inner(innerParam){
console.log(outerParam + " " + outerVar + " " + innerParam);
}
}
var globalVar = outer("say");
globalVar("to all");//say hello to all
Here the outer function is outer
and its execution context has the variable outerParam
and outerVar
.
The inner function is inner
and its execution context has the variable innerParam
.
The statement var globalVar = outer("say");
pulls out the inner
function along with the reference to the execution context of it's parent function inner
and assigns it the variable globalVar
. Due to this when globalVar
is invoked it is actually a copy of the inner
function, it is able to access it's parent's execution context even though parent function's execution has ended and ideally variables in the parent function's execution context should have been garbage collected. This phenomenon is called as Closure
Example 2:
var x = function(){
var y = 10;
return function(g){
return y + g;
}
}
var z = x();
x= null;
console.log(z(9));//19
Here we see even though we explicitly remove the reference to the anonymous function which was earlier referenced by the variable x
, the closure function referenced by z
is still able to access it.
Example 3:
Closure are useful to make Function factory. For example if we wish to create an addition factory where we want to generate a function which would return the sum of a constant number and the supplied number.
function AdditionFactory(num){
return function(a){
return num + a;
}
}
var add2 = AdditionFactory(2);
console.log(add2(2));//4
var add3 = AdditionFactory(3);
console.log(add3(2));//5
Example 4:
function closureReferenceOuter(){
var varToRefer = 100;
return {//here an object is returned which contains the closure functions
setVarToRefer: function(newVar){
varToRefer = newVar;
},
getVarToRefer: function(){
return varToRefer;
}
}
}
var tempClosure = closureReferenceOuter();
console.log(tempClosure.getVarToRefer());//100
tempClosure.setVarToRefer(101);
console.log(tempClosure.getVarToRefer());//101
Closure store references to outer function's variable and the actual value is not stored. If the outer function's variables get modified in some way, the closure function will also reflect it
Example 5: Closures can be used to emulate private variable in JavaScript.
function MyClass(){
var counter = 0;
return {
incrementCounter : function(){
++counter;
},
decrementCounter : function(){
--counter;
},
getCounter : function(){
return counter;
}
};
}
var newCounter = MyClass();
console.log(newCounter.getCounter());//0
newCounter.incrementCounter();
newCounter.incrementCounter();
newCounter.incrementCounter();
console.log(newCounter.getCounter());//3
newCounter.decrementCounter();
console.log(newCounter.getCounter());//2
In the above example there is no way to directly access the counter
variable which makes it a private variable.
Example 6:
Let us see one more very common example where the usage of closure, seem to cause inconsistencies in the code.
function buildFunctions(){
var arr = [];
for(var i = 0; i < 3; i++){
arr.push(
function(){
console.log(i);
}
);
}
return arr;
}
var fs = buildFunctions();
fs[0]();//3
fs[1]();//3
fs[2]();//3
Ideally we expect this to print 0, 1 and 2 but due to closure the reference to "i" in the functions pushed to the array does not end even after the function gets added to the array in the Nth position and every time i++ is executed the value in the functions already pushed to the array also gets updated.
So to solve this we need to constrict or restrict the scope of the variable i
. Here the new JavaScript ES6 programming construct called let
can be used to achieve the desired result:
The
let
statement declares a block scope local variable, optionally initializing it to a value.
function buildFunctionsSafe(){
var arr = [];
for(let i = 0; i<3; i++){
arr.push(
function(){
console.log(i);
}
);
}
return arr;
}
var fss = buildFunctionsSafe();
fss[0]();//0
fss[1]();//1
fss[2]();//2
Or another way of rectifying the code is to use Immediately Invoked Function Expression in conjugation with closure:
function buildFunctionsSafeClosure(){
var arr = [];
for(var i = 0; i<3; i++){
arr.push(
(function(j){
return function(){
console.log("correct " + j);
}
}(i))
);
}
return arr;
}
var fssc = buildFunctionsSafeClosure();
fssc[0]();//correct 0
fssc[1]();//correct 1
fssc[2]();//correct 2
Callback functions in JavaScript are also closures
Callback functions derived from programming paradigm called FUNCTIONAL programming which specifies use of functions as arguments. Callback functions are closures - As the callback function is passed as an argument to another function, it will be executed at some point inside the containing function's body as if the callback function was defined in the containing function body. This is how callbacks behave as closures and have access to the containing function's variables, parameters and also the global scope
Event handlers for DOM elements are also closures.
So now we have a fair idea of what are closures, how and why they behave the way they do.
In the next post we will look in details about JavaScript operators.
References
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures