Understanding Closures In JavaScript

Understanding Closures In JavaScript

In this article, I will be explaining the concept behind closure in the simplest way possible. I will use a few examples to make things clearer. Before we can fully grasp what closures mean in JavaScript we will need to understand some concepts in JavaScript.

To begin we will have to look at these topics in JavaScript:

  • Scope
  • Scope Chain
  • Lexical Environment
  • Factory Functions
  • Immediately Invoked Function Expression (IIFE)

Now to get started, create a JS file app.js. This is where we would write our code and use the browser console to display values.

Scope

The context in which values and expressions are "visible" or can be referenced. MDN

Scope simply means the area within our JavaScript code in which a variable can be accessed or referenced. There are three(3) major types of scope in JavaScript;

  • Global scope
  • Local scope
  • Block scope

Global Scope

When you write your code directly to your app.js file, you are currently in the global scope. Any variable defined here is available in any part of your code in your JS file.

Global variables are deleted or cease to exist when you close the browser window or tab.

Let's look at this example;

const name = "Joe";
console.log(name); // returns Joe

we can create a function here also and call it;

const name = "Joe";
console.log(name); // returns Joe

function printName(){
    console.log(name);
}

printName(); // returns Joe

Notice we were able to run our application smoothly, printName function was able to access the name variable without issues. Let's define a variable inside printName function and see if we can access it outside the printName function.

const name = "Joe";

function printName() {
  const lastName = 'Manuel';
    console.log(name); //returns "Joe"
  console.log(lastName); //returns "Manuel"
};

console.log(name); //returns "Joe"
console.log(lastName); //returns a Reference Error

We encountered an error trying to access the lastName outside the printName function body where it was created. Why is this? Well, to answer this let's take a look at what local scope mean.

Local scope

This refers to a separate scope not included in the global scope. Variables, functions and statements are only available in a restricted scope. Variables defined within a JavaScript function is local and only available to that function body. It can never be accessed outside that function.

So we see why we got an error trying to access lastName in the global scope which was defined in the local scope (inside a function).

Local scope enables us to define variables with the same name in different scopes without any issues as they are in different scopes and will not clash with each other.

function PrintName(){
  const firstName = 'John';
  console.log(firstName);
}

function LogName(){
  const firstName = 'Manuel';
  console.log(firstName);
}
PrintName() // returns "John"
LogName() // returns "Manuel"

When our app.js file is executed and our printName function runs, the variable defined in it is created and immediately the function finish running the variable is deleted.

Block Scope

This is another type of scope which is offered basically by variables defined in a {} block. Block quote is essentially a local scope and its variables are only avialable to be used within the {} block and not accessible outside. Block scope is only provided when you define your variable with const and let. Variables defined with var do not provide block scoping but allow the variable defined to be scoped to the outer scope with which the block statement is created.

Block Statements like if statements and for loops provide this block scoping.

if (true) {
  const firstName = "John";
  console.log(firstName); //returns John
}
console.log(firstName); //returns a Reference Error, there is no firstName defined in the global Scope
// block statement 1
if (true) {
  var firstName = "John";
  console.log("inside block statement-1", firstName); // within the block scope so returns "John"
}
console.log("in the global scope",firstName); // firstName is scoped to its outer surrounding scope which is the global scope and so returns "John"

function printName(){

//  block statement 2
  if (true) {
  var lastName = "Doe";
  console.log("inside block statement-2",lastName); // within the block scope so returns "Doe"
}
  console.log("inside printName Function",lastName) // lastName is scoped to its outer surrounding scope which is the local scope created by the printName function and so returns "Doe"
}

printName()

console.log("inside global scope",lastName) // returns a Reference Error, as lastName is not globally scoped

Always use ES6 method of defining variables with let and const as this is a good practice.

Now we know that a variable can either be globally, locally or blocked scope. Global scope exists within the duration your application is live while local scope will exist within the time a function is called and it finish executing.

Frame 1 (1).jpg

Scope Chain

We learnt that when a variable that is defined in global scope is used in a function, the function is able to access this variable because this variable was defined globally and hence available to all parts of the code with no restriction. What of group of nested functions can they access a variable that isn't declared in their scope?

For example;

function computeSum() {
    const a = 2;
    function sum() {
        const b = 7;
        function add() {
            const c = 1;
            return a + b + c;
        }
        return add();
    }
    return sum();
}

const result = computeSum();
console.log(result); // returns 10

In the example above, computeSum is a compound function that houses both sum and add function. The add function is deeply nested inside this compound function and works with two foreign variables and one local variable. As we discussed earlier, functions will always search outside their local scope for variables they need to operate that was not defined in their local scope.

We notice that variable a is defined in computeSum function body, b was defined in sum function body and c defined in add function body. The add function had to go up two(2) level to get the value of a and up one(1) level to get the value of b. This is called a scope chain.

Scope chain is the hierachy of scopes that will be searched in order to find a function or variable.

Lexical Environment

Now, after understanding the ideology of scope chain, we can move on to what lexical environment means. It is simply the environment or scope that a nested function has access to. This is a phenomenon used to describe when a nested function has access to the variables defined by its parent scope (function).

Anytime JavaScript tries to execute a function it creates a new lexical environment. To create a lexical environment, JavaScript scans the function and does two(2) things:

  • creates an environment record which stores variables(or function) declared in that function body.
  • stores a reference to the outer environment which gives it access to the parents own lexical environment.

So a lexical environment could be pictured to look like this;

LEXICAL ENVIRONMENT = {
   [environmentRecord]: {
        variableIdentifier: <variable>,
        functionIdentifier: <function>
   },
   [outerEnvironment]: <reference to the parent lexical environment>
}

Now let's go back to our previous code to throw more light on this;

function computeSum() {
    const a = 2;
    function sum() {
        const b = 7;
        function add() {
            const c = 1;
            return a + b + c;
        }
        return add();
    }
    return sum();
}

const result = computeSum();
console.dir(result); // returns 10

Let's define the lexical Environments in this code;

COMPUTE-SUM LEXICAL ENVIRONMENT = {
   [environmentRecord]: {
        variableIdentifier: a,
        functionIdentifier: sum
   },
   [outerEnvironment]: <global lexical environment>
}
SUM LEXICAL ENVIRONMENT = {
   [environmentRecord]: {
        variableIdentifier: b,
        functionIdentifier: add
   },
   [outerEnvironment]: <computeSum lexical environment>
}
ADD LEXICAL ENVIRONMENT = {
   [environmentRecord]: {
        variableIdentifier: c,
   },
   [outerEnvironment]: <sum lexical environment>
}

Now we should see that the deeply nested add function has access to the lexical environment of the sum function which in turn has access to the computeSum lexical environment which in turn has access to the global lexical environment.

Factory Functions

These are functions that return another function. Observe the code below does not call(or execute) the returned function. This gives the returned function sole access to manipulate the variables defined in the parent function. This method is used in JavaScript to create private variables that are not accessible to other developers working on the same project.

Let's modify our code with an example. Say a company taxes its workers based on years of experience with the company and each following year they increase the worker salary by the previous year tax amount. We can give a sole access to a function that can access the salary and make salary private so nobody can increase the salary from another part of the code.

function calculateSalaryTax() {
    let salary = 1200;
    console.log(`salary at the beginning => ${salary}`); //returns the value of salary let's call this point A
    return function calculateTax(year) {
        const rate = 0.19;
        const tax = salary * rate * year;
        salary += tax;
        console.log(`salary after calculating tax=> ${salary}`); //returns value of after the operation let's call this point B
        return tax;
    };
}
const result = calculateSalaryTax(); //return salary at the beginning => 1200
result(2)
result(3)
result(4)

log2.png

After calculateSalaryTax runs the first time, it's variable should not be available again( this is what we learnt with local variables created by functions) but result is able to access it and even change it multiple times, why and how is this happening? Before I answer this question, let's discuss one more concept.

Immediately Invoked Function Expression (IIFE)

As the name implies this refers to functions that are immediately invoked or called as soon as the code starts to run. In our previous example we had to call, calculateSalaryTax and save it to result but in IIFE we don't need to do this. Consider the example below;

const result = (() => {
    let salary = 1200;
    console.log(`salary at the beginning => ${salary}`); //returns the value of salary let's call this point A
    return (year) => {
        const rate = 0.19;
        const tax = salary * rate * year;
        salary += tax;
        console.log(`salary after calculating tax=> ${salary}`); //returns value of after the operation let's call this point B
        return tax;
    };
})()

result(2);
result(3);
result(4);

result is assigned to an anonymous arrow function which is wrapped in () and then called immediately with () at the end. This makes the anonymous function to quickly execute and store its returned function to result immediately the code starts running. This saves us some line of code compared to the initial way we wrote it when discussing factory functions. Same thing happens here, the anonymous function executes and the salary variable suppose to be deleted along side it but we discover that result can still access it. Let's now learn what closure really is.

Closure

When JavaScript runs a script and gets to a function, after it finish executing that function it deletes that function and every variable that was defined in it (It's execution context). But JavaScript is smart to observe if there is any child function that uses a variable defined in the parent function. It saves the required variable and only that variable to a closed scope that only the function that requires it can access it.

Therefore, JavaScript during execution allows the returned function to close over a variable it requires that isn't defined locally but outside its surrounding function.

A closure is a function having access to the parent scope, even after the parent function has closed. - w3schools

function calculateSalaryTax() {
    let salary = 1200;
    console.log(`salary at the beginning => ${salary}`); //returns the value of salary let's call this point A
    return function calculateTax(year) {
        const rate = 0.19;
        const tax = salary * rate * year;
        salary += tax;
        console.log(`salary after calculating tax=> ${salary}`); //returns value of after the operation let's call this point B
        return tax;
    };
}
const result = calculateSalaryTax(); //return salary at the beginning => 1200

calculateTax becomes a closure after calculateSalaryTax has finished executing, this why it is able to access the salary variable. when calculateSalaryTax is called, it does all these and assigns it to the result variable as refence to calculateTax function, so we get a chance to actually see it in our console. Use console.dir() to print result to the console.

function calculateSalaryTax() {
    let salary = 1200;
    console.log(`salary at the beginning => ${salary}`); //returns the value of salary let's call this point A
    return function calculateTax(year) {
        const rate = 0.19;
        const tax = salary * rate * year;
        salary += tax;
        console.log(`salary after calculating tax=> ${salary}`); //returns value of after the operation let's call this point B
        return tax;
    };
}
const result = calculateSalaryTax(); 
console.dir(result);

log3.png

We see that 3 scopes are defined, closure which houses our salary variable, script which refers to a reference to calculateTax function and it's local scope and the global scope. Now we see, how result is able to manipulate and store a new value of salary all this while.

Also note that it is only the variable that is required in closure function that is closed any other variable not used by the function is not closed( is deleted).

function calculateSalaryTax() {
    let salary = 1200;
    let workerName = "Joe";
    return function calculateTax() {
        console.log(salary);
    };
}
const result = calculateSalaryTax();
console.dir(result);

log4.jpg

I only used the salary variable hence calculateTax function closes around that variable and doesn't close on the workerName variable that wasn't used inside it.

function calculateSalaryTax() {
    let salary = 1200;
    let workerName = "Joe";
    return function calculateTax() {
        console.log(salary);
        console.log(workerName);
    };
}
const result = calculateSalaryTax();
console.dir(result);

log5.jpg

Here, both salary and workerName variable were used so calculateTax function closes around the both variables.

Finally

We learnt a lot and can now differentiate between the terms used to describe a variable's scope in JavaScript. I hope you enjoyed this article and now understand closures better.