How you can learn Closures in JavaScript and understand when to use them
Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris
if you are like me, you hear concepts like lexical environments, closure, execution context and you're like yep I heard it, can't remember what they are but I'm probably using it. And you know what, you'd be correct. You are most likely using it but who can remember these terms anyway?
I mean most likely the only time we need to know what the name of these terms is, is when we need to study up for an interview in JavaScript. I'm not saying don't learn the concepts, I'm saying as long as you know how they work the world won't implode if you call them something else:
We know that we need to know these terms at the point of the interview, the rest of the time we just need to know how things work when we code, and we do.
Let's dig deeper, how come we can understand and even apply these terms but not know what they are called? Is it bad naming? Maybe, in my case, it's about realizing I'm a visual learner and I need an image to remember things by, or it doesn't stick..
So welcome to a crazy ride into my brain - let's talk Closures 😃 - All aboard the crazy train 😉
Closures
What are closures? Closures are a function bundled with its lexical environment.
Thank you professor but I barely understood a word
Ok, let's look at some code:
function outer() {
// lexical environment
let a = 1;
return function inner(b) {
return a + b
}
}
2
3
4
5
6
7
What you are seeing above is a function outer()
enclosing another function inner
. It's not only enclosing inner()
but also the variable a
.
What's so great about this?
Even after the function outer()
has stopped executing the function inner()
will have access to its lexical environment, in this case, the variable a
.
Lexical environment, sounds like lexicon, huge and heavy books. Show me.
Ok, imagine we call the code like this:
const fn = outer();
fn(5) // 6
2
Above it remembers a
to have value 1
.
Ok, so it's like it treats
a
as a private variable, but in a function?
Yea, precisely.
I have an idea how to remember this 😃
Yes?
Cows
Cows?!
Yes Cows in an enclosure with the outer function as the enclosure and the cows as the inner function and the private variable, like this:
Oook, slooowly stepping away.
What can we use them for
Ok so we got some intro to closure, but let's state what we can use them for:
Creating private variables, we can create a lexical environment long after the outer function has finished executing, this enables us to treat the lexical environment as if it were private variables in a class. This enables us to write code like this:
function useState(initialValue) { let a = initialValue; return [ () => a, (b) => a = b]; } const [health, setHealth] = useState(10); console.log('health', health()) // 10 setHealth(2); console.log('health', health()) // 2
1
2
3
4
5
6
7
8
9Above we see how we return an array that exposes methods both for returning and setting the variable
a
from the lexical environmentPartial application, the idea is to take an argument and not apply it fully. We've shown that in our very first example but let's show a more generic method
partial()
:const multiply = (a, b) => a * b; function partial(fn, ...outer) { return function(...inner) { return fn.apply(this, outer.concat(inner)) } } const multiply3 = partial(multiply, 3); console.log(multiply3(7)) // 21
1
2
3
4
5
6
7
8
9
10The above code collects all the arguments for the first function
outer
and then it returns the inner function. Next, you can invoke the return value, as it is a function, like so:console.log(multiply3(7)) // 21
1Ok, I get how this works I think. What about an application, when do I actually use it?
Well, it's a bit of an academic construct, it's definitely used in libraries and frameworks though.
That's it?
I mean, you can make functions more specialized using it.
Just one example?
Sure, here is one:
const baseUrl = 'http://localhost:3000'; function partial(fn, ...args) { return (...rest) => { return fn.apply(this, args.concat(rest)) } } const getEndpoint = (baseUrl, resource, id) => { return `${baseUrl}/${resource}/${id ? id: ''}`; } const withBase = partial(getEndpoint, baseUrl); const productsEndpoint = withBase('products') const productsDetailEndpoint = withBase('products', 1) console.log('products', productsEndpoint); console.log('products detail', productsDetailEndpoint);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18The above is quite a common scenario, constructing a URL endpoint. Above we create a more specialized version with
withBase
that is partially applying thebaseUrl
. Then we go on to add the specific resource idea like so:const productsEndpoint = withBase('products') const productsDetailEndpoint = withBase('products', 1)
1
2It's not a thing you have to use, but it's nice and can make your code less repetitive. It's a pattern.
Isolate part of your code/pass the JavaScript interview, for this one let's first show a problem that is very common in JS interviews. I got the same question asked to me in three interviews in a row. The question can also be found if you Google it. Cause guess what, that JavaScript interview process is broken.
What do you mean broken?
Nobody cares if you have many years of experience doing this and that and knows a bunch of frameworks. Instead, the interviewers usually spend 5 minutes googling JavaScript questions to ask you.
Sounds like they are asking about the JavaScript language and it's core concepts. Isn't that good?
Yea that part is good, but JavaScript has so much weirdness to it there's a reason Crockford wrote a book called JavaScript the good parts, and that it's a very thin book. There are definitely good parts to it but also a lot of weirdness.
You were going to tell me about an interview problem?
Right, so here's the code, can you guess the answer?
for (var i = 0; i < 10; i++) { setTimeout(() => { return console.log(`Value of ${i}`); }, 1000) }
1
2
3
4
51,2,3,4,5,6,7,8,9,10
Not hired.
That's cold, can you tell me why?
setTimeout
is asynchronous and is called after1000
milliseconds. The for-loop executes right away so that by the timesetTimeout
is called thei
parameter will have it's maximum value10
. So it prints10
,10
times. But we can fix it so it prints it in an ascending way.How?
By creating a scope, an isolation in the code, like so:
for (var i = 0; i < 10; i++) { ((j) => setTimeout(() => { return console.log(`Value of ${j}`); }, 1000))(i) }
1
2
3
4
5The above creates an Immediately Invoked Function Expression, IIFE (It does look iffy right 😉 ? ). It accomplishes isolation whereby each value of
i
is bound to a specific function definition and execution.There is an alternative to the above solution, using
let
. Thelet
keyword creates a scoped code block. So the code would instead look like so:for (let i = 0; i < 10; i++) { setTimeout(() => { return console.log(`Value of ${i}`); }, 1000) }
1
2
3
4
5Thank you Quozzo for pointing this out.
Summary
Ok, so this whole closure business is about Cows and fences and privacy
And JavaScript 😉