DEV Community

Cover image for How you can learn Closures in JavaScript and understand when to use them
Chris Noring for ITNEXT

Posted on • Updated on

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:

Bear claw

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 ;) 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:

Cows in an enclosure

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)

Collapse
 
quozzo profile image
Quozzo

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.

Collapse
 
softchris profile image
Chris Noring

good point will add that :)

Collapse
 
z2lai profile image
z2lai

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:

for (var i = 0; i < n; i++) {
    // ...
}

becomes

var i;    // declared here
for (i = 0; i < n; i++) {    // initialized here
    // ...
}

So the 10 console logs would be referring to the same "globally" scoped variable after it's been incremented to 10.

Collapse
 
stereoplegic profile image
Mike Bybee • Edited

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.

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).

Collapse
 
frankfont profile image
Frank Font

Bless you Mike! I wish more folks thought this way.

Collapse
 
codewilling profile image
Sean Flores

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.

Collapse
 
softchris profile image
Chris Noring

it's just first line, you forgot =. Should read

const multiply = (a, b) => a * b;

Collapse
 
codewilling profile image
Sean Flores

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. :-).

Collapse
 
naskkotech profile image
Olohundare Nasirudeen • Edited

You could use .bind function in replace of apply

const baseUrl = 'http://localhost:3000';

  const getEndpoint = (baseUrl, resource, id) => {
      return `${baseUrl}/${resource}/${id ? id: ''}`;
  }

const withBase = getEndpoint.bind(null, baseUrl);
const productsEndpoint = withBase('products')
const productsDetailEndpoint = withBase('products', 1)