JavaScript basics 101 - closures

JavaScript basics 101 - closures

JavaScript Closures are one of those strange (but very powerful) concepts to grasp. I'll walk you through this topic for an easy understanding.

Photo by Jorge Ramirez on Unsplash

A multitude of people has explained this topic already. The common problem I found with closures is: it looks more complicated than it is.

That's why I created this guide, with simple examples and a lot of information!

We'll be covering the basics of closures, how we create one, and some examples that can be applied in real life. Before we dive deeper...

What exactly are they?

A closure gives you access to variables within a function's scope outside of it.

It happens if you create a function with some variables and an inner function referencing those variables.

That's a mouthful, but don't worry. Take a look at this example:

function outer() {
  let a = 'Hello Closures'

  function inner() {
    console.log(a)
  }

  inner()
}

outer()

It prints "Hello Closures" on the console.

The inner function doesn't have local variables. It has access to the outer function's variables thanks to the scope.

When you call outer, inner gets called. You can't change or alter the variables inside the parent function.

The problem with the above pattern is: you are calling a function for calling another function.

A best practice for doing the same thing is returning the inner function. Then, you can have access to it with another variable. Let's look at an example:

function outer() {
  let a = 'Hello Closures'

  function inner() {
    console.log(a)
  }

  return inner
}

const myFirstClosure = outer()

myFirstClosure() // 'Hello Closures'

First, you create the outer function and its variables, then, you create another function inside it, and return it. Calling the outer function in a variable gives us access to the inner function.

That's a closure in JavaScript.

In other programming languages, local variables get destroyed after function execution. It is not the case with JS.

The inner function has access to its environment, including variables declared inside of the parent function. As we returned the inner function, we can interact with its environment.

Closures let you bind functions with the data they use. It ensures you don't end up changing the data outside of the function.

Let's make a more visual example

Think of a counter.

We want to create two methods for this counter, increase and decrease. We also need available the count number.

With closures, this is a simple task:

function initCounter() {
  let count = 0

  function increase() {
    count++
  }

  function decrease() {
    count--
  }

  function getCounterValue() {
    return count
  }

  return {
    increase,
    decrease,
    getCounterValue
  }
}

const counter = initCounter()

The counter variable takes the three methods that we created inside our function. Thanks to this, you CAN'T change the count without calling the method. We call this data privacy or encapsulation.

Now that you know how closures on the surface, what's the expected output of this code?

// all the other code from above

let count = 5

counter.increase()
counter.increase()
counter.decrease()
counter.increase()
counter.decrease()

counter.getCounterValue()

If you said 1, I did a good job explaining closures.

In JavaScript, closures prefer local variables over global ones. If you re-initialize the count outside of the closure, it won't affect it. This feature is huge for data that may have a common name, or you use the same logic repetitively.

If you have some programming experience in JavaScript, closures resemble classes.

In fact, I would say (on the basic level), both are interchangeable. Closures win a point because you don't have to deal with the this keyword.

Bonus

You may be thinking

Yes, I understand a closure now, but why should I care?

Closures are a nice feature in JavaScript. The variables on a closure are immutable outside of the function. It comes in handy when you try to use the same logic for different parts of your applications.

Think of a Tweet. Twitter has two main counters, retweets, and likes. The counter function we just created works wonderfully for this example.

function initCounter() {
  // Our same function as before
}

const retweets = initCounter()
const likes = initCounter()

// We make some retweets and some likes

retweets.increase()
retweets.increase()

likes.increase()
likes.increase()
likes.increase()


console.log(`LIKES: ${likes.getCounterValue()}`, `RT: ${retweets.getCounterValue()}`)

Can you guess the output?

"LIKES: 3", "RT: 2"

The same logic applies a counter in two different places, with two different count values. It is powerful in multiple situations.

I hope you found this insightful and learned a ton!

See you at the next one.