To learn closures, you need to understand how scope works.
In JavaScript, we have global and local scope.
Variables declared in the main body are referred to as global scopes. They belong to the global object and can be accessed from anywhere in the code.
Variables declared in a function are referred to as local scopes. They belong to the function body including its nested functions, and they can access any variable in the global scope.
If a function is defined inside a function, the parent function will not have access to the variables declared in the child function. But the child function will have access to the variables in the parent function.
So basically any block of code has access to the variables of its outer scope.
Here is an example
const x = 'someone';
function incrementFrom(count) {
// has access to x
return count++;
}
const firstCall = incrementFrom(0);
const secondCall = incrementFrom(5);
console.log(firstCall);
console.log(secondCall);
// does not have access to count i.e console.log(count) will throw an error
Have in mind, that the parameter is a local variable because it's in a local scope. It will get created anew for every call to the incrementFrom
function.
It is basically the same thing as
function incrementNum() {
let count = 5;
return count++;
}
// all the calls
So the good news is that local variables will not trample each other when the function is called.
But the bad news is, with such standard calling incrementFrom(5)
a couple of times will not increment it. It's just going to keep logging 5, because "every local variable is created anew for every call".
So what if we want to keep making use of the value passed into (or created in) a function for each call to this function. Like in the case of incrementFrom()
, we simply want to get an initial value and increment on it for every call.
So when I call incrementFrom(3)
3 times, it will magically increment from 3 to 4, then to 5, and to 6. This is possible with closures.
Another example is probably to pass in a firstName
of a user to a function, and then later add the lastName
to it. Something like
function printName(firstName, lastName) {
return `${firstName} ${lastName}`;
}
For some reasons, lastName
has not been provided yet, so you make the first call with what you have now
console.log(printName('John', "I don't have it yet"));
Finally, the lastName
get's processed, and now you have nowhere in memory with firstName
, so you'll end up doing this to make the second call with the lastName
.
console.log(printName('I lost it', 'Doe'));
I know, the example is kinda stupid, but the idea is to make two function calls and relate their local variables together. This is possible with closures.
Now what are closures?
In Eloquent JavaScript it says
β¦being able to reference a specific instance of a local binding in an enclosing scope is called closure.
Simply put, closures are functions that have access to the scope of outer functions even when the outer functions have closed (no longer active).
That means a child function can make use of any local variable declared in a parent function anytime, even after the parent function has been called and is no longer active.
The way it works is this, when we create a function with any local variable, the function returns another function (which is a child function), and as stated the child function has access to the variable in the parent function.
So when the function is called, the value is a function, which can be called. i.e
function callMe() {
return () => 'Hello world';
}
const funcVal = callMe();
console.log(funcVal());
This is just an expression of "functions are what they return", or better expressed as "function as values".
So when a function that returns a string is called, the properties and methods of strings can be used on that function call. Same thing with numbers, arrays, objects, functions.
In this case, our function is returning a function, which means the value of the callMe()
function can be called, because it is a function (you can add params and args).
This is where it gets more interesting...
function callMe(val) {
return (newVal) => val + newVal;
}
const funcVal = callMe(2);
console.log(funcVal(2)); // 4
We've called the callMe()
function once and passed in a value. Now, this value can be used when we call the function it returns. This is closure.
I can call funcVal()
different times now and it will still have access to the local variable (val
) of the parent function (callMe
)
console.log(funcVal(3)); // 5 i.e 2 + 3
console.log(funcVal(10)); // 12 i.e 2 + 10
// we can go on and on
Now the rule of local variables of a function not trampling themselves on different calls still STANDS, we've only made one call to the parent function callMe
, let's try calling it one more time
const funcVal = callMe(2);
const funcVal2 = callMe(100); // local variable (val) will be created anew here with a value of 100.
console.log(funcVal(2)); // 4 i.e 2 + 2
console.log(funcVal2(10)); // 110 i.e 100 + 10
So basically, it's the function they return that does the magic. Even at that, their local variables are still not trampling each other on different calls
console.log(funcVal(3)); // 5 i.e 2 + 3
console.log(funcVal(10)); // local variable (newVal) will be created anew here, but it still has access to the local variables in the outer function. so we get 12 i.e 2 + 10
Now let's head back to our initial examples or problems. Let's first solve the name issue.
Recall we had a function printName
to print a user's first name and last name, but for some reason, the last name will be delayed (and we know about this) so we have to go on without it initially. And then when it finally comes we should print the full name. This is what we'd do
function printName(firstName) {
return (lastName) => `${firstName} ${lastName}`;
}
Now the function has changed a little bit
- the function
printName
now takes just one argument (firstName
- the guy we know will not be delayed) - it (
printName
) now returns a function instead of returning a string. - and the child function takes
lastName
(the guy which we know will be delayed) and then returns the full name string.
If we try logging, it will now make more sense
// first name comes
const user = printName('John');
// after a while, last name comes
console.log(user('Doe')); // John Doe
Voila!! Problem solved using closures. Let's add another user
// first name comes
const user = printName('John');
// after a while, last name comes
console.log(user('Doe')); // John Doe
// new user
const user2 = printName('Sarah');
console.log(user2('Michelle')); // Sarah Michelle
I know there are tons of other ways to solve this, but this is yet another way.
Now the last example before we call this article a wrap - our counter.
Recall we had a function incrementFrom
which is in no way incrementing. How do we solve this?
function incrementFrom(count) {
return () => count++;
}
Just one thing changed, we returned a function that returns count + 1
instead of returning just count + 1
.
Now let's see if it works
const addOne = incrementFrom(5);
console.log(addOne()); // 5
console.log(addOne()); // 6
console.log(addOne()); // 7
console.log(addOne()); // 8
// and on and on
Gladly and unsurprisingly, it works!!
So this is how closures can be very useful in programming.
Conclusion
Now, this is quite a lot to take in, if it's your first time learning about closures. But when you practice over time it will become clear.
Thanks for reading to the end, I hope you enjoyed and learned from it as I did. I'll see you next time. But in the meantime, you can leave comments for me to let me know what you think. You can also click the like and share button so we can reach more developers.
Let's connect, hit me up on Twitter @elijahtrillionz
Top comments (4)
Finally, the concept of closures has started making sense to me after reading this article π₯°π₯°π₯°
I'm glad it was helpful
I understood, I like the easy way you explained us.
Thanks. Glad you liked it.