Understanding JavaScript closure and JavaScript scope by@nirjan
2,265 reads

Understanding JavaScript closure and JavaScript scope

November 1st 2022
8 min
by @nirjan 2,265 reads
tldt arrow
Read on Terminal Reader

Too Long; Didn't Read

To get how a JavaScript Closure and JavaScript Scope work, we need to understand what we mean by scope. Scope is a set of rules that tell you where you can access your variables and functions from.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Understanding JavaScript closure and JavaScript scope
Nirjan.dev HackerNoon profile picture

@nirjan

Nirjan.dev

#developer 💻 | #Football ⚽ fan| #cinephile 🎬 | lifelong...

react to story with heart

What is a JavaScript closure and JavaScript scope

In this post, I explain how a JavaScript closure works and what does JavaScript scope mean. In the end, I will also give some examples of JavaScript Closures in action.

What is scope in JavaScript?

To get how a JavaScript Closure and JavaScript Scope work, we need to understand what we mean by scope. Scope is a set of rules that tell you where you can access your variables and functions from.

Where you set your variables and define your function will influence where you can access them from. Here is an example of that in JavaScript.

const firstVariable = 1;
]
function firstFunction() {
    return 2;
}

function anotherFunction() {    
    const secondVariable = 2;
    
    function secondFunction() {
        return 3;
    }
}


// will log 1 2
console.log(firstVariable, firstFunction()) 

// Uncaught ReferenceError: secondVariable is not defined
console.log(secondVariable, secondFunction()) 

So what's the deal here? Why can can we access the firstVariable and the firstFunction but not the secondVariable and the secondFunction? This is because of how JavaScript Scope works.

How does scope work in JavaScript

JavaScript only had function scope (with a few exceptions) until ES6 came out. This meant that whenever you defined a new function, you created a new scope. A variable defined within a function cannot be accessed from outside the function. This is why we get an error when trying to log the secondVariable value and call the secondFunction.

const firstVariable = 1;
]
function firstFunction() {
    return 2;
}

function anotherFunction() {    
    const secondVariable = 2;
    
    function secondFunction() {
        return 3;
    }
}


// will log 1 2
console.log(firstVariable, firstFunction()) 

// Uncaught ReferenceError: secondVariable is not defined
console.log(secondVariable, secondFunction()) 

JavaScript Scopes are nested

The inner scope has access to the outer scope, but the outer scope can't access the variables set in the inner scope.

function outerFunction() {
  let i = 777;
  console.log(i); // 777
  console.log(b); // Refference Error (outer scope can't access variables in inner scope)

  function innerFunction() {
    console.log(i); // 777 (inner scope can access the enclosing outer scope)
    let b = "hello";
  }

  innerFunction();
}
outerFunction();

Scope consists of a series of bubbles that each act as a container or bucket in which identifiers are declared , these bubbles nest inside each other and this nesting is defined at author time.

Kyle Simpson (Author of YDKJS series)

image

image

//global scope
var i = 666;
console.log(i); // 666

function foo() {
  // scope of foo
  var i = 777;
  console.log(i); // 777
}

foo();

How does JavaScript Scope work in ES6

ES6 introduced two new ways to declare variables using let and const which can be used for block scoping. Whenever you use let or const to declare variables, they are scoped to the block (any code surrounded by a pair of curly brackets {}). ES6 also introduced another scope with modules, but closures work in the same way in modules too.

//global scope
var i = 666;
console.log(i); // 666

{
  // new block scope
  // you'll probably see these types of blocks used with
  // if,else,for,while,etc rather that used like this
  let i = 777;
  console.log(i); // 777
}

note: You might have heard the term lexical scope in JavaScript which is just a technical term for scope that is defined during the code compilation process called lexing. It basically means that scope is defined when you are writing the code (declaring functions, or blocks), instead of when it is actually executed.

What is a Closure?

A closure is a programming technique for allowing functions to access values that are defined outside of it. It is the combination of a function bundled together with variables that are outside of it.

You have a closure when a function accesses variables defined outside of it.

What is a JavaScript Closure?

In JavaScript, because scope is nested, it is actually pretty easy to create a closure.

let usernames = ['John', 'Jack', 'James', 'Jhonny'];
let searchTerm = 'Ja';

// Here the function is accessing the searchTerm variable which is outside the function
// this is a closure
let filteredUsernames = usersnames.filter(function (username) { 
    return username.startsWith(searchTerm)
  });

Since, functions can be nested, you can also use closures with nested functions like this:

function outerFunction() {
  let a = 666;
  function innerFunction() {
    console.log(a);
  }
  return innerFunction;
}
let myFunc = foo(); // now myFunc is just a reference to innerFunction inside the outerFunction.

myFunc(); // 666;

When you call the outer function, the 'a' variable gets set to 666. So, You might expect the 'a' variable to not be available after the outer function gets called. But the variable is inside the scope of the outer function. Since scope can be nested, the inner function still has access to the 'a' variable. So, as long as some part of the code can still call the inner function, it can still remember the 'a' variable.

Closures can cause functions to remember variables within their scope. With Scope, you are combining a function with the data that it can access outside of it.

Examples of closures in JavaScript

Now that you know what a closure is, how can you use it? Closures are useful to combine a function with some internal state that it should use. This might be familiar if you have some experience with object-oriented programming.

function getClickCountUpdater() {
  let counter = 0;
  return function() {
    counter = counter + 1;
    console.log(counter);
  };
}

const updateClickCounter = getClickCountUpdater();

// now to change the counter, you need to use this function, you can't change it directly
updateClickCounter(); // 1;
updateClickCounter(); // 2;

counter = counter+1; // ReferenceError: counter is not defined
console.log(counter); // ReferenceError: counter is not defined

// You can create another count updater function which will have a seprate counter variable in it's scope
const anotherCounter = getClickCounUpdater();
anotherCounter(); // 1;

Closures can also be useful for creating different variations of a function. It is also useful to keep track of some value within a function that shouldn't be changed from anywhere else. This is pretty useful when you want to do some logging for a specific function like this:

function createAd(width, height) {
  let adCounter = 0;
  return function (link) {
    adCounter++;
    console.log(`${width}x${height} ad created ${adCounter} times!`);
    return {
      _type: 'ad',
      link: link,
      width: width,
      height: height,
      // other generic ad options
    }
  }
}

const create300x250Ad = createAd(300, 250);
const create728x90Ad = createAd(720, 90);

// 300x250 ad created 1 times!
const new300x250Ad = create300x250Ad('https://linkToTheAd.com/path');

// 300x250 ad created 2 times!
const another300x250Ad = create300x250Ad('https://linkToTheAd.com/path');

What is the benefit of closure in JavaScript?

Closures can also be used to do some performance optimization. Let's say you have a function that needs to create access a variable but that that variable takes up a lot of memory. Instead of creating that variable each time you run the function, you could use a closure to reuse it.

function findAdLinkById(id) {
  const adMap = {
    'e645-2456': 'https://link.com/path',
    '2282-3238': 'https://another-link.com/another-path',
    // 1000 more items
    
    '2901-2192': 'https://one-more-link.com/path'
  }
  return adMap[id]
} 

// whenever this function gets called it needs to store a new adMap value in memory which makes calling this function slower the more we call it
const firstAd = findAdLinkById('e645-2456')
const secondAd = findAdLinkById('2282-3238')
const thirdAd = findAdLinkById('2901-2192')

function createFindAdLinkById() {
  const adMap = {
    'e645-2456': 'https://link.com/path',
    '2282-3238': 'https://another-link.com/another-path',
    // 1000 more items
    
    '2901-2192': 'https://one-more-link.com/path'
  }
  return (id) => adMap[id]
} 
const findAdLinkById = createFindAdLinkById()

// whenever this function gets called it reuses the same adMap value which makes it faster to run
const firstAd = findAdLinkById('e645-2456')
const secondAd = findAdLinkById('2282-3238')
const thirdAd = findAdLinkById('2901-2192')

Note: while this will be more performant, it will also use more memory. So, if you also need to optimize the memory consumption then you should try something else.

One of the most common use cases of closures is with asynchronous JavaScript.

let id = 1;

getUserFromAPI(id).then(response => {
  // this works because the function callback here can still remember the value of the id even though this code won't run immediately
  console.log(`Fetched user ID: ${id}`)
})

Most of the time, you might not even realize when you're using a closure. Basically, anytime you're using a variable that is outside a function from inside the function, you are using a closure.

So, These are some practical use cases for closures:

  • Limiting the access to certain variables
  • Limiting the access to call certain functions
  • Keeping track of values across function calls
  • Logging function calls
  • Optimizing the memory usage of function calls
  • Creating different variations of the same function
  • Remembering values with asynchronous functions that get executed later

Closures can cause performance issues

While closures can be pretty useful when you use them intentionally. Sometimes they can also lead to memory issues, because closures are stored in memory. If we create unnecessary closures frequently, then it can cause a memory leak.

We need to make sure we're actually using the variables that are being stored in the memory. We should not create unnecessary closures too much. Let's take a look at our example from earlier to see how closures can cause too much memory usage.

function createFindAdLinkById() {
  const adMap = {
    'e645-2456': 'https://link.com/path',
    '2282-3238': 'https://another-link.com/another-path',
    // 1000 more items
    
    '2901-2192': 'https://one-more-link.com/path'
  }
  return (id) => adMap[id]
} 


// now instead of having just one closure, we have 2000 closures
for (let i = 0; i <= 2000; i++) {
  const findAdLinkById = createFindAdLinkById()
  const firstAd = findAdLinkById('e645-2456')
  const secondAd = findAdLinkById('2282-3238')
  const thirdAd = findAdLinkById('2901-2192')
}

So, Whenever you are creating closures, you need to be aware of how much memory you are actually using. Let me know if you have any other questions about closures.

image

image

This article was originally written as a blog post on my personal site.

RELATED STORIES

L O A D I N G
. . . comments & more!
Hackernoon hq - po box 2206, edwards, colorado 81632, usa