This content originally appeared on Level Up Coding - Medium and was authored by Darryl Mendonez
A Secret Bag of Tricks
The idea of a JavaScript Closure can be a bit difficult to wrap your head around. Often closures are used when you have a function that returns another function. The magic behind them is even though a function is returned, thus eliminating the calling function and its local variables, the returned function still retains the ‘memory’ of its deleted environment including the destroyed local variables, and can use them as if they still exist 🤯
Let's demystify closures by breaking down the following code:
1: var add = (function () {
2: var counter = 0;
3: return function () {counter += 1; return counter}
4: })();
5: const increment1 = add();
6: const increment2 = add();
7: const increment3 = add();
8: console.log('increments: ', increment1, increment2, increment3); // output -> increments: 1 2 3
Code taken from w3schools’ JavaScript Closures page and modified to add a console.log
Before any scripts can be run, the global execution context (GEC) is pushed into the call stack.
The script runs and variable add along with its function expression is created within the GEC. Notice that the function assigned to add is an immediately invoked function expression (IIFE) that returns an anonymous function: function() {counter += 1; return counter}
Interesting… Why write the function this way? Hmm… Why not set add to equal a function with the definitioncounter += 1; return counter directly instead of having it equal to an IIFE that returns that. In fact, you can define counter outside the function and rewrite the function like so and the output would be just the same.
var counter = 0;
var add = function() {
counter += 1;
return counter;
};
Astute observation detective. Let’s keep walking down the yellow brick road and see where else this adventure takes us.
Now, at this point, more JavaScript magic occurs. IIFE’s, as its name implies, invokes as soon as it’s defined. This means that var add does in fact now equal function() {counter =+ 1; return counter}. Poof, just like that, the IIFE is now gone… Oh, interesting… But now so is var counter = 0;.
So why are we jumping through hoops when we could have just wrote out the function as mentioned before?
var counter = 0;
var add = function() {
counter += 1;
return counter;
};
This is much simpler and easier to understand. Plus, with the IIFE, counter looks like it was never declared. How would it even execute without causing an error?
Both great questions. Yes, rewriting the code as such would be much simpler to understand and yes it would still produce the intended outcome. But the way that counter is declared makes it a global variable. We only want it to be accessible by the addfunction. Rewriting it this way pollutes the global scope and leaves it unprotected for other functions to access and possibly alter. We’re trying to avoid both of these.
Okay, how about this?
var add = function() {
var counter = 0;
counter += 1;
return counter;
}
Oh wait, that would reset the counter to 0 every time it’s called. Okay, I see the disadvantages so tell me more about what’s going on over here…
var add = function() {
counter += 1;
return counter
};
Great, so the IIFE executed and returned a function that increments counter by 1 and then returns it. The IIFE ceases to exist as does its local variable or so it seems. The magic of the returned function is that it remembers its environment including local variables even after it’s destroyed and can continue to use them. So, in our case, counter was declared in the body of the IIFE and once executed the declaration vanished. The returned function, however, remembers counter’s declaration even though it is no longer in the global execution context. Normally, when a function fires and needs to find the value of a variable it’s manipulating, it will first check its local scope, then its parent’s, and then grandparent’s… all the way up until it reaches the global scope. This is called scope chain. When add fires though, before it checks its local scope, it will first check its secret bag of tricks. And sure enough, it will find counter in it.
When add is called on line 5, it will see that counter was initially assigned to 0. It will then increment counter’s value by 1, and return its value while setting increment1 = 1.
When it is called on line 6, it will check its bag of tricks again and see that counter is now 1, increment and return it again, and assign increment2 = 2…
…and so on for the next line. All the add functions have executed and are popped off the stack. The console.log runs and finally outputsincrements: 1 2 3.
You can view your bag of tricks, otherwise known as closure scope, by using the console in your Chrome browser’s dev tools. Just pass your closure into a console.dir() and expand the [[Scopes]] property.
Closure functions have a magic ability. They’re able to encapsulate data that gets popped off the stack, keep them in a deeper level than what normal functions have in their scope chain, and use them exclusively and without concern that they’ve been touched by any other function ✨💫✨
JavaScript Closures was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Darryl Mendonez
Darryl Mendonez | Sciencx (2021-11-17T14:03:30+00:00) JavaScript Closures. Retrieved from https://www.scien.cx/2021/11/17/javascript-closures-2/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.