Notes Javascript - Closures
Closures
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
function runScript1(){ let divResult = document.getElementById("results1"); let strResult = ""; const counter = (function () { let privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment() { changeBy(1); }, decrement() { changeBy(-1); }, value() { return privateCounter; }, }; })(); strResult = "counter.value = "+ counter.value(); // 0. counter.increment(); counter.increment(); strResult += "<br/>counter.value = "+ counter.value(); // 2. counter.decrement(); strResult += "<br/>counter.value = "+ counter.value(); // 1. divResult.innerHTML = strResult; }
Run Script 1Response
function runScript2(){ let divResult = document.getElementById("results2"); let strResult = ""; const makeCounter = function () { let privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment() { changeBy(1); }, decrement() { changeBy(-1); }, value() { return privateCounter; }, }; }; const counter1 = makeCounter(); const counter2 = makeCounter(); strResult = "counter1.value = "+ counter1.value(); // 0. counter1.increment(); counter1.increment(); strResult += "<br/>counter1.value = "+ counter1.value(); // 2. counter1.decrement(); strResult += "<br/>counter1.value = "+ counter1.value(); // 1. strResult += "<br/>counter2.value = "+ counter2.value(); // 1. counter2.increment(); counter2.increment(); counter2.increment(); counter2.increment(); strResult += "<br/>counter2.value = "+ counter2.value(); // 1. divResult.innerHTML = strResult; }
Run Script 2Response
Closure Scope Chain
Every closure has three scopes:
- Local scope (Own scope)
- Enclosing scope (can be block, function, or module scope)
- Global scope
A common mistake is not realizing that in the case where the outer function is itself a nested function, access to the outer function's scope includes the enclosing scope of the outer function—effectively creating a chain of function scopes. To demonstrate, consider the following example code.
// GLOBAL SCOPE const e = 10; function sum(a) { return function (b) { return function (c) { // OUTER FUNCTIONS SCOPE return function (d) { // LOCAL SCOPE return a + b + c + d + e; }; }; }; } console.log(sum(1)(2)(3)(4)); // 20
function runScript3(){ let divResult = document.getElementById("results3"); let strResult = ""; // GLOBAL SCOPE const e = 10; function sum(a) { return function sum2(b) { return function sum3(c) { // OUTER FUNCTIONS SCOPE return function sum4(d) { // LOCAL SCOPE return a + b + c + d + e; }; }; }; } const sum2 = sum(1); /* sum2 = the function sum2(b), a = 1 */ const sum3 = sum2(2); /* sum3 = sum2 from prev, b = 2 */ const sum4 = sum3(3); /* sum4 = sum3 from prev, c = 3 */ const result = sum4(4); /* result = sum4 from prev, d = 4 */ /* result = (a = 1) + (b = 2) + (c = 3) + (d = 4) + (e = 10) 1 + 2 + 3 + 4 = 10 10 + 10 = 20 */ strResult = "result = "+ result; // 20 divResult.innerHTML = strResult; }
Run Script 3Response
In the example above, there's a series of nested functions, all of which have access to the outer functions' scope. In this context, we can say that closures have access to all outer function scopes.
Closures can capture variables in block scopes and module scopes as well. For example, the following creates a closure over the block-scoped variable y:
function outer() { const x = 5; if (Math.random() > 0.5) { const y = 6; return () => console.log(x, y); } } outer()(); // Logs 5 6
function outer() { const x = 5; if (Math.random() > 0.5) { const y = 6; return () => chartCoords(x,y); } } function chartCoords(x, y) { return "Coords - ("+ x +","+ y +")"; } function runScript4(){ let divResult = document.getElementById("results4"); let strResult = ""; divResult.innerHTML = outer()(); }
Note that this will fail roughly 50% of the time because of the Math.random test. In a failure situation, the second () will not exist.
Run Script 4