DEV Community

Elijah Trillionz
Elijah Trillionz

Posted on

Learn JavaScript Closures in 7 mins

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}`;
}
Enter fullscreen mode Exit fullscreen mode

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"));
Enter fullscreen mode Exit fullscreen mode

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'));
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}`;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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++;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Discussion (2)

Collapse
oscarromero profile image
Oscar Romero

I understood, I like the easy way you explained us.

Collapse
elijahtrillionz profile image
Elijah Trillionz Author

Thanks. Glad you liked it.