Closures

Hey ,

I'm thrilled to help you learn JavaScript. Unfortunately, you've landed on a page where you cannot access with your current purchase.

Please upgrade (use this link) access this content.

I'm super eager to help you learn more!

Closures

A closure is created when you have two functions—one of these functions is inside the second function.

// Closure pattern
function outside () {
  function inside () {/* ... */}
}

How closures work

Closures prevent variables declared within outside from leaking out into the global scope.

function outside () {
  const message = 'Hello World!'
}

// Run the function
outside()

// Tries to log message
console.log(message)
Error.

But they let inside use the variables declared within outside.

function outside () {
  const message = 'Hello World!'

  function inside () {
    console.log(message)
  }

  // Runs inside
  inside()
}

outside() // Runs outside
Logs 'Hello World!'.

Returning the inside function

Closures become powerful when you return inside. When you do this, you can run inside later.

function outside () {
  const message = 'Hello World!'
  return function inside () {
    console.log(message)
  }
}

Closures remember values within outside's lexical scope. When you run inside, you still have access to the variables declared within outside.

const inside = outside()
inside() // Hello World!
Logs 'Hello World!'

Passing values into closures

You can pass values into outside to store them. You can then use them later when you need to.

function outside (message) {
  return function () {
    console.log(message)
  }
}

const inside = outside('Hello World!')
inside() // Hello World!
Logs 'Hello World!'

Inside can have parameters too

If you wish to pass arguments into inside, you can return a function that takes in arguments.

function outside (message) {
  return function (additionalMessage) {
    console.log(message + ' ' + additionalMessage)
  }
}

const inside = outside('Hello')
inside('World!') // Hello World!
Logs 'Hello World!'.

Arrow functions

You can use arrow functions to create closures. No worries there! Arrow functions make things easier to read since there’s no function keyword overhead.

function outside (message) {
  return () => {
    console.log(message)
  }
}

const inside = outside('Hello World!')
inside() // Hello World!
Logs 'Hello World!'.

Remember to follow the Arrow Functions Best Practice I explained in This in JavaScript!

Returning an object

A closure is actually created when you nest a scope within another scope (not just functions in functions). We don’t usually recognise them as closures, but they are closures.

So if you return an object as inside, you return a closure too. The object contains information about outside's lexical scope.

function outside (message) {
  return {
    message
  }
}

const inside = outside('Hello World!')
console.log(inside.message) // Hello World!
Logs 'Hello World!'.

This pattern is often used in Object Oriented Programming, where you return properties and methods you want to expose. (Hello Factory Function!)

function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayFirstName () {
      console.log(firstName)
    }
  }
}

const zell = Human('Zell', 'Liew')
zell.sayFirstName() // Zell
console.log(zell.lastName) // Liew

When you use closures, you don’t need to use this at all! The instance remembers all declared variables because they’re inside a closure.

Where to use closures

Closures can be used in callbacks

Let’s say we have a list of elements. When we click on each element, we want to console.log the element’s text content.

One way to do this is to create a textContent variable. We will store the text content inside this textContent variable. Then, we log textContent inside an event listener.

const list = document.querySelectorAll('li')

for (const li of list) {
  const textContent = li.textContent
  li.addEventListener('click', () => {
    console.log(textContent)
  })
}

In this case, each callback remembers its textContent value. When you click the li, they will log this value—even if the actual text was changed later.

Closures can be used for Partial Application

We learned this while going through the bind method. Here, we can use bind to predetermine variables we want to pass into a function.

function sayName (firstName, lastName) {
  console.log(`${firstName} ${lastName}`)
}

const sayZellName = sayName.bind(null, 'Zell')
sayZellName('Liew') // Zell Liew

Closures can also be used to hide information in OOP

We’ll explore this use case in the next lesson.