loading...

Closures: The JS Scope Hack

steelvoltage profile image Brian Barbour ・3 min read

Closures in JS (3 Part Series)

1) Closures: The JS Scope Hack 2) Closures: Using Memoization 3) Closures: Debouncing

After a function is invoked and finishes its mission, it is removed from the call stack. Being removed means its variable environment should also be, right?

Not in all cases, because closures exist in Javascript.

Understanding how closures work will help you leverage their powers and become an even better Javascript programmer. Hell, I tapped into closures often before I fully understood them. I think a lot of newer programmers tend to do that, following examples and code alongs. It's an easily overlooked feature of the language, but amazing in the right hands.

Before diving in it's important to get a little context behind the two main things that give closures power.

The first thing that plays is into closures is the fact that functions are "first class citizens." You probably heard this before, the term is always thrown around. I find it vague myself, only giving an implication of the importance of functions not what it means mechanically.

Functions behave like a data type. Because, in the end, they are objects. This is with most structures in Javascript. Because their objects they can be manipulated as such. You can assign a function to a variable, creating a function expression. Functions also can be passed as arguments into other functions or returned as values. Doing either of those actions is how you create a higher order function.

Here's a simple example of a higher order function, as a refresher.

const calculateSalesTax = stateTaxRate => {
  return cost => {
    return cost * stateTaxRate
  }
}

const newYorkTax = calculateSalesTax(1.08875)
const northCarolinaTax = calculateSalesTax(1.0475)

console.log(newYorkTax(30.0).toFixed(2)) // returns 32.66
console.log(northCarolinaTax(30.0).toFixed(2)) // returns 31.43

The first arrow function is assigned to the variable calculateSalesTax and when invoked it kicks off another arrow function that takes in cost as a parameter and spits out the final calculation. We reuse this function to calculate sales tax in two different states. Helps our code stay DRY.

Lexical scoping allows the Javascript engine to know, even before we run our code, what variables each function has access to. The "lexical" part means that it's based on where things are written in the code, rather than where they are ran.

I think of closures as little bins inside a function where variables get tossed into to await being accessed by functions within the same scope.

const checkSecret = () => {
  const secret = 'pillow' // gets tossed into the bin! 
  return attempt => {
    return secret === attempt
  }
}

const attempt = checkSecret()

console.log(attempt('blanket')) // returns false
console.log(attempt('pillow')) // returns true

console.log(checkSecret()) // Returns Function. Note: We can't get to our secret variable! Probably a good thing, since its secret after all. 

The above example shows a closure in action. Even after checkSecret has run and returned the arrow function, Javascript can still reach in to the bin and grab the secret variable to do the comparison. The returned function spits out our boolean.

The above example also illustrates one of the main advantages of closures. Protecting variables! We can't get to secret at all from the outside, only the Javascript engine can.

I saw someone describe this notion a course I took. Basically, closures are moral responsibility. If a parent function gives birth a child function, it must provides everything the child needs survive and complete its purpose.

With a basic understanding, you can see how closure is a bit of a hack to the way we think scope works. It's one we can leverage to protect our variables.

There are many other ways we can wield closures, which I plan on covering in future blog posts as a part of a series. So make sure you follow me, if you're interested.

Closures in JS (3 Part Series)

1) Closures: The JS Scope Hack 2) Closures: Using Memoization 3) Closures: Debouncing

Posted on Jul 1 '19 by:

steelvoltage profile

Brian Barbour

@steelvoltage

Software Engineer at Community Brands and Javascript enthusiast.

Discussion

markdown guide
 

Using closures this way is a nice touch! It's true that the secret variable inside checkSecret can't be changed, however I would be careful not to add anything too secret this way, as checkSecret.toString() returns

"() => {
  const secret = 'pillow' // gets tossed into the bin! 
  return attempt => {
    return secret === attempt
  }
}"

thus revealing the secret string.

 

👍 Right! Put actual secrets into .env variables.