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
}
}
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
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
Above we see how we return an array that exposes methods both for returning and setting the variable a
from the lexical environment
-
Partial 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
The 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
Ok, 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);
The above is quite a common scenario, constructing a URL endpoint. Above we create a more specialized version with withBase
that is partially applying the baseUrl
. Then we go on to add the specific resource idea like so:
const productsEndpoint = withBase('products')
const productsDetailEndpoint = withBase('products', 1)
It'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,5,6,7,8,9,10
Not hired.
That's cold, can you tell me why?
setTimeout
is asynchronous and is called after 1000
milliseconds. The for-loop executes right away so that by the time setTimeout
is called the i
parameter will have it's maximum value 10
. So it prints 10
, 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)
}
The 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
. The let
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)
}
Thank you Quozzo for pointing this out.
Summary
Ok, so this whole closure business is about Cows and fences and privacy
And JavaScript ;)
Top comments (9)
You don't need an IIFE if you declare a variable within the loop using let, because unlike its var counterpart, let is scoped to a code block and not the function.
good point will add that :)
I think along with explaining why the wrong answer to the interview questions is due to setTimeout being asynchronous, you should also include that var doesn't obey block scope AND it behaves like the following when the same variable is declared multiple times within the same scope such as when declared in a for loop:
becomes
So the 10 console logs would be referring to the same "globally" scoped variable after it's been incremented to 10.
This is one reason I don't bother with terminology quizzes in interviews. They tell me nothing about what a dev can do (and probably give the impression that I'm looming over them with some smug sense of superiority, or at best that I nitpick over stupid crap).
Bless you Mike! I wish more folks thought this way.
Thanks for the write-up. I'm trying to deeply understand every operation that you cite, but I'm getting an error in this example. What am I doing wrong? jsbin.com/basapo/1/edit?js,console. Thanks.
it's just first line, you forgot
=
. Should readconst multiply = (a, b) => a * b;
Thank You! that's what I thought, but I thought you were writing this line with a JS feature I didn't know. I copied and pasted it from your article, you may want to change it in your article also. :-).
You could use .bind function in replace of apply