☑️ What
When it comes to closures or any complex topic, I find that the most effective way to learn is through an example. However, to explain what closures are I must use some complex concepts like lexical environment and scope chain which, by the way, I might have promised in the previous article that I will cover. So, it is a good opportunity to resolve that promise. 😇
Let's have a look at this example code and analyze it.
function a() {
const name = 'Shai';
return function b() {
const age = 32;
return function c() {
const city = 'Tel-Aviv';
return `${name} is ${age} years old amd he lives in ${city}`
}
}
}
// a() // function b
// a()() // function c
a()()(); // "Shai is 32 years old amd he lives in Tel-Aviv"
When function 'a' gets invoked, the JS engine creates a new execution context and pushes that function to the call stack.
With the call stack, the JS engine can keep track of where the code is in its execution, or in other words, which execution context is currently running.
Execution context tells us which lexical environment (lexical means at compile time, where the function is written) is currently running and in every execution context, we get the 'this' keyword, arguments and variable environment.
In our example, we first call function 'a' => a(). As a result, a new execution context is created and function 'a' gets pushed to the stack.
The lexical environment of function 'a' is the global environment because it located in the global environment.
Then, we call function 'a' again => a()(), a new execution context is created and function 'b' gets pushed to the stack.
Function 'b', however, wrapped by function 'a', therefore its lexical environment is function 'a' and the global environment.
In the third call => a()()() function 'c' gets returned, a new execution context is created, and function 'c' gets pushed to the stack.
Function 'c' located inside function 'a' and function 'b', therefore its lexical environment is function 'a', function 'b' and the global environment.
In other words, function 'c' is lexically scoped inside function 'b', which is lexically scoped in function 'a'.
Lexical scope is the available data and variables where the function was defined (NOT where it was called) and it determines our available variables.
Variables defined inside a function are not accessible from outside the function, which means that the variables in function 'b' and function 'c' are not accessible to function 'a', and the variables in function *'c' are not accessible to function *'b'.
But, function 'b' has access to global variables and the variables which were defined in function 'a', and function 'c' has access to the global variables,
variables which were defined in function 'b' and function 'a'.
I know, it's very confusing, so I made a chart which I hope makes it clear.
This is possible thanks to the scope chain.
When in the call stack, we are in the execution context of function 'c', which his variable environment is 'city' and it is lexically scoped inside function 'b'
and function 'a' - it has access to the variables of those functions.
return `${name} is ${age} years old and he lives in ${city}`
So, when the JS engine gets to this line above, first it searches for the 'name' variable in the local variable environment of function 'c',
when it does not find the variable, the JS engine goes up on the scope chain to function 'b'. The 'name' variable is not declared in function 'b',
so we go again up on the scope chain to function 'a', where we find the declaration of the variable 'name'.
This works the same with variable 'age'. About the 'city' variable, as its part of the variable environment of function 'c'', there is no need to search
it in the outside world - no need to goes up on the scope chain.
⛔️ Pause: I know, you feel I am throwing on you a lot of concepts, while I did not even begin to explain what closures are, but I promise that soon it will be clear.
With that knowledge, let's make a closure with closures and, finally, explain what they are.
Closure is the combination of a function and the lexical environment from which it was declared. It allows a function to access variables from an enclosing scope or environment even after it leaves the scope in which it was declared.
After function 'a' gets invoked, pushed to the stack and popped of the stack, its variable environment ('name') remains in memory,
which means it does not get collected by the garbage collector because another function - function 'c' has a reference to it.
The same thing happens with function 'b', and that's why even after those functions get popped off the stack, function 'c' still has access, through the scope chain, to the 'name' and 'age' variables.
The JS engine knows, before we get to the line in the code in which we call function 'a', which function has access to which variables and save those variables.
NOTE: if we have another variable in function 'b', which is not in use by function 'c', the JS engine will not save it in the closure box and it is gonna be garbage collected.
const something = 'something';
☑️ How
By 'how' I mean how we create a closure (or how to use it).
We can create closures due to the fact that in JavaScript, functions are first-class citizen, which means that functions can be returned from another function and functions can be passed as an argument to another function.
Therefore, in order to use a closure, define a function inside another function and expose it by returning or passing it to another function.
If we take a look again at our example, we can see the structure of closures:
a function that gets returned from another function, while the returned function has access and uses variables from the outer function variable environment.
☑️ Why
Closures have 2 main benefits:
1. Memory efficiency
In the following example, we have a function that creates very big array every time it gets called (because nothing is referencing that function and its variable, so it gets collected by the garbage collector);
function heavy(index) {
const bigArr = new Array(9000).fill('😈');
console.log('created!');
return bigArr[index];
}
heavy(889); // 'created!' '😈'
heavy(889); // 'created!' '😈'
heavy(889); // 'created!' '😈'
With closures, we have a way to only create the array once:
function notHeavy() {
const bigArr = new Array(9000).fill('😈');
console.log('created again!');
return function(index) {
return bigArr[index];
}
}
const closureHeavy = notHeavy();
closureHeavy(889); // 'created again!' '😈'
closureHeavy(889);
closureHeavy(889);
The function 'notHeavy' gets called and because its inner anonymous function uses one of its variables: 'bigArr', this variable does not get collected by the garbage collector. As the anonymous function located lexically inside the 'notHeavy' function, it has access to its variables and can goup on the scope chain.
Now, we can call 'notHeavy' just once, save the result in a variable, and with that variable, call the function again (the anonymous function).
The array will be created only once (and we can confirm it by running the code above and see that we get the console.log only once), and that's why
it saves memory.
2. Encapsulation
const makeBomb = () => {
let pauseTime = 0;
const passedTime = () => pauseTime++;
const totalPauseTime = () => pauseTime;
const launch = () => {
pauseTime = -1;
return '💥';
}
setInterval(passedTime, 1000);
return {
totalPauseTime
}
}
const bombBtn = makeBomb();
bombBtn.totalPauseTime(); // 0
bombBtn.totalPauseTime(); // 30 - the seconds that have passed until I run the function again
With encapsulation, we can hide information that is unnecessary to be seen or manipulated by the outside world.
This follows the principle of least privilege - a big security principle when it comes to programming, where you don't want to give just anybody access to your API.
We don't want anyone to be able to launch a bomb 💥, so we don't expose the 'lunch' method. We only expose the 'totalPauseTime' method by creating a closure and returning it.
Thanks for reading. I hope you now know better what closures are, how to create and use closures, and why use closures.
Link to the original post:
https://syntactic-sugar.netlify.app/closures
Top comments (1)
🤩 Wow, thank you. I really appreciate your comment and learned from it.