Closure
Garbage collector
In previous examples, we have considered what happens with the call stack, thread and the variable environment.
Consider the example below:
function incrementCounter() {
let count = 0;
return ++count;
}
incrementCounter();
incrementCounter();The function incrementCounter is invoked in two different places. On each call a new local execution context is created and a local variable count is declared. When the return statement is reached the execution context is closed. JavaScript has a mechanism under the hood called the garbage collector, which comes along and removes any variables from memory that are not being used anymore. JavaScript does this automatically but this is not the case in other languages. In the example above, once the local execution context for incrementCounter has been closed then the count variable is removed from memory by the garbage collector.
In this way incrementCounter is a misnomer as its return value is always fixed as 1. It would be desirable to have a function that would instead return consecutive numbers on each sucessive call.
Returning Functions
Consider the example below:
function makeCounter() {
function innerCounter() {
let count = 0;
return ++count;
}
return innerCounter;
}
const counter = makeCounter();
counter();
counter();In this example, we have a function makeCounter and its return value is a new function. makeCounter is said to be a higher order function as it returns a function. We are assigning a reference to this function to counter. Every time this new function counter is invoked a new execution context is created and a new local counter is created as in the previous example.
However, we can make a subtle yet highly important change to the previous example in the following way:
function makeCounter() {
let count = 0;
function innerCounter() {
return ++count;
}
return innerCounter;
}
const counter = makeCounter();
counter();
counter();In the above example, count is declared in the outer function makeCounter. makeCounter then returns the innerCounter function and makeCounter's local execution context is closed: however, the innerCounter function itself now references a variable in the parent scope.
Despite the fact that the innerCounter is exposed to the global, because of lexical scope the function innerCounter can still reference the variables in the outer scope which means the count variable is not garbage collected in this example. This is an example of closure in javascript.
Definition: Closure is when a function is able to access its outer lexical environment (where it was declared) even when that function is executed elsewhere. An inner function will always have access to the parent function's variable even after the parent function's local execution context has closed.
Using Closure
Consider the next example below:
function createTimesTwo() {
function multiplyBy2(n) {
return n * 2;
}
return multiplyBy2;
}
const doubleNum = createTimesTwo();
const result = doubleNum(50);- We create a function called
createTimesTwoin the global VE - We create a variable called
doubleNumin the global VE. Its value is currentlyundefinedas it is being assigned to a function invocation. In other words, a local execution context will be opened and the return value ofcreateTimesTwowill be saved into the variabledoubleNum - The return value of
createTimesTwois a function, namely a reference to the functionmultiplyBy2 - We are saving the return value of
doubleNuminvoked with an argument of 50, into the variableresult
We can adapt the example we have just written above in order to exploit closure.
Here is a slightly different function createMultiplier:
function createMultiplier(mult) {
function multiplyNum(num) {
return num * mult;
}
return multiplyNum;
}
const multiplyBy5 = createMultiplier(5);
const result = multiplyBy5(9);- We create a function called
createMultiplierin the global variable environment (VE) - We create a variable called
multiplyBy5in the global VE. Its value is currentlyundefinedas it is being assigned to a function invocation. As with the previous example the return value ofcreateMultiplierwill be assgined tomultiplyBy5. - When the local execution context for
createMultiplieris closed the variablemultis not garbage collected as it is being referenced bymultiplyNum. This is closure - where the inner function's reference to the outer function's variables still persists, even if this inner function is returned out ofcreateMultiplierand exposed to the global. - The variable
multis said to be stored in a COVE, standing for closed over variable environment. - When
multiplyBy5is invoked later on it attempts to referencemultand closure means it will look for this variable in the COVE. Therefore asmulthas been assigned a value of 5 the variablenuminmultiplyNumwill always be multipled by 5.
This example shows how we can use closure to create a more generic function that can create a function that could multiply by any number - dependent on what we pass as an argument to createMultipler.
Enhanced functions
A particular use of closure could be to create new functions whose behaviour is restricted or controlled in some way. Suppose we wanted a new function add3TimesOnly which will only return a sum a limited number of times. Any subsequent calls to add3TimesOnly would return undefined.
add3TimesOnly(10, 32); // returns 42
add3TimesOnly(4, 20); // returns 24
add3TimesOnly(50, 100); // returns 150
add3TimesOnly(34, 17); // returns undefinedSo the add3TimesOnly function can only return the sum three times. Lets consider how to generate such a function:
const add = (a, b) => a + b;
function createLimitedFunc(func, maxCalls) {
function limitedFunc(a, b) {
return func(a, b);
}
return limitedFunc;
}
const add3TimesOnly = createLimitedFunc(add, 3);
add3TimesOnly(10, 32);
add3TimesOnly(4, 20);
add3TimesOnly(50, 100);
add3TimesOnly(34, 17);We can break down what is happening in the above example:
addandcreateLimitedFuncare functions stored in the global variable environmentadd3TimesOnlyis assigned to an invocation ofcreateLimitedFunc. The value ofadd3TimesOnlyis temporarily undefined until we have got the return value ofcreateLimitedFunc.createLimitedFuncis invoked with 2 arguments,funcwill point toaddandmaxCallswill now have a value of3.- In the local execution context for
createLimitedFunc, the functionlimitedFuncis declared and then returned. The local execution context forcreateLimitedFuncis closed.
In the above example, the return value of add3TimesOnly is the return value of func. In other words, whatever func returns is what add3TimesOnly or limitedFunc returns. In this way the limitedAdd function behaves exactly like the add function. However, there is no limit at the moment to how many times the limitedFunc can return func(a,b). We can introduce some conditional logic into the limitedFunc in order to ensure that the limitedFunc only returns the func(a,b) as many times as the passed maxCalls. We can see this below:
const add = (a, b) => a + b;
function createLimitedFunc(func, maxCalls) {
function limitedFunc(a, b) {
if (--maxCalls >= 0) return func(a, b);
}
return limitedFunc;
}
const limitedAdd = createLimitedFunc(add, 3);
limitedAdd(10, 32);
limitedAdd(4, 20);
limitedAdd(50, 100);
limitedAdd(34, 17);Every time the limitedFunc is invoked then the maxCalls that is stored in the COVE gets decremented and we can only return func(a, b) provided the maxCalls is bigger.