How you can learn Closures in JavaScript and understand when to use them

Chris Noring - Jul 2 '20 - - Dev Community

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player