Skip to content

2.5 Closures

Task 1: Let's create the washing machine function that returns clean clothes.

function washingMachine(){
  var startCleaning = true;
  var cleanClothes = "The clothes are clean now!";

  return cleanClothes;
}

var isClean  = washingMachine();
console.log(isClean);

The result is: The clothes are clean now!

How does the code above work?

  1. The JavaScript engine declares and initializes the function washingMachine.
  2. It assigns undefined to isClean.
  3. It starts executing the code from top to bottom.
  4. It assigns the return value of washingMachine to isClean.
  5. The function washingMachine returns "The clothes are clean now!".
  6. The value of isClean is displayed in the console.

Task 2: Let's create the extra cleaning function.

function extraCleaning(){
  var startExtraCleaning = true;
  var cleanExtraClothes = "The clothes are extra clean now!";
  return cleanExtraClothes;
}  


var isClean  = extraCleaning();
console.log(isClean);

The result is: The clothes are extra clean now!

Task 3: Let's add our extra cleaning function inside our washing machine function.

function washingMachine(){
  var startCleaning = true;
  var cleanClothes = "The clothes are clean now!";

  function extraCleaning(){
    var startExtraCleaning = true;
    var cleanExtraClothes = "The clothes are extra clean now!";
    return cleanExtraClothes;
  }  

  return extraCleaning;
}

var isClean  = washingMachine();
console.log(isClean());

The result is: The clothes are extra clean now!

Do not panic! We just put the task 2 code inside the task 1 code. Instead of returning the cleanClothes value from washingMachine, we return the extraCleaning function.

isClean value is the extraCleaning function. So we need to call isClean to get the return extraCleaning value.

It all makes sense now:

  1. We turn the washing machine on
  2. The regular cleaning process starts var startCleaning = true.
  3. The clothes are clean now var cleanClothes = "The clothes are clean now!"
  4. But here we need to do extra cleaning, so the return value of washingMachine is the extraCleaning function.
  5. The clothes are being cleaned by the regular process, then it enters another cleaning process, which we call the extra cleaning
  6. The return value of extraCleaning function is "The clothes are extra clean now!".

Task 4: Refer to task 3; can we access the variable startCleaning outside the function washingMachine. Prove your answer with written code.

function washingMachine(){
  var startCleaning = true;
  var cleanClothes = "The clothes are clean now!";

  function extraCleaning(){
    var startExtraCleaning = true;
    var cleanExtraClothes = "The clothes are extra clean now!";
    return cleanExtraClothes;
  }  

  return extraCleaning;
}

var isClean  = washingMachine();
console.log(isClean());

console.log(startCleaning);

The result is as follows:

The clothes are extra clean now!
Uncaught ReferenceError: startCleaning is not defined

Well, it is expected. startCleaning is a local variable. You can not access local variables from the global scope.

Task 5: Refer to task 3; can we access the variable startCleaning inside the function extraCleaning. Prove your answer with written code.

function washingMachine(){
  var startCleaning = true;
  var cleanClothes = "The clothes are clean now!";

  function extraCleaning(){
    var startExtraCleaning = true;
    var cleanExtraClothes = "The clothes are extra clean now!";

    console.log(startCleaning);

    return cleanExtraClothes;
  }  

  return extraCleaning;
}

var isClean  = washingMachine();
console.log(isClean());

The result is as follows:

true
The clothes are extra clean now!

Yes! We can access startCleaning inside the function extraCleaning. Of course, the inner process/function needs to be aware of the outer process/function.

Task 6: Refer to task 3; can we access the variable cleanClothes‍‍ inside the function extraCleaning. Prove your answer with written code.

function washingMachine(){
  var startCleaning = true;
  var cleanClothes = "The clothes are clean now!";

  function extraCleaning(){
    var startExtraCleaning = true;
    var cleanExtraClothes = "The clothes are extra clean now!";

    console.log(cleanClothes);

    return cleanExtraClothes;
  }  

  return extraCleaning;
}

var isClean  = washingMachine();
console.log(isClean());

The result is as follows:

The clothes are clean now!
The clothes are extra clean now!

Yes! We can access cleanClothes inside the function extraCleaning. Indeed, the extraCleaning function needs to be aware of the cleanClothes variable.

Task 7: Refer to task 3; can we access the variable startExtraCleaning inside the function washingMachine. Prove your answer with written code.

function washingMachine(){
  var startCleaning = true;
  var cleanClothes = "The clothes are clean now!";

  function extraCleaning(){
    var startExtraCleaning = true;
    var cleanExtraClothes = "The clothes are extra clean now!";

    return cleanExtraClothes;
  }  

  console.log(startExtraCleaning);
  return extraCleaning;
}

var isClean  = washingMachine();
console.log(isClean());

The result is as follows:

Uncaught ReferenceError: startExtraCleaning is not defined

It makes sense; why would an outer process/function be aware of the inner process variables?! It is not needed. Remember that you can not access a local variable outside its function. startExtraCleaning is not accessible outside extraCleaning function.

Task 8: Refer to task 3; can we access the variable cleanExtraClothes inside the function washingMachine. Prove your answer with written code.

function washingMachine(){
  var startCleaning = true;
  var cleanClothes = "The clothes are clean now!";

  function extraCleaning(){
    var startExtraCleaning = true;
    var cleanExtraClothes = "The clothes are extra clean now!";

    return cleanExtraClothes;
  }  

  console.log(cleanExtraClothes);
  return extraCleaning;
}

var isClean  = washingMachine();
console.log(isClean());

The result is as follows:

Uncaught ReferenceError: cleanExtraClothes is not defined

It is an expected result. You can not access a local variable outside its function scope.

Why are not the local variables accessible outside their functions?

Well! It is because of the lifetime of the variables.

The lifetime of the variables means when were they born? and when will they die?

The variable is born when you declare it. The local variables die when their function finishes the execution. The global variables die when you close the browser tab.

Task 9: We create a function that takes a value. It processes this value by adding 5 to it. And it returns another function that processes the value again by dividing the resultant value over 2.

function processValue(value){
  var process1Result = value + 5;

  function processValueAgain(){
     var process2Result = process1Result / 2;

     return process2Result;
  }

  return processValueAgain;
}

var finalResult = processValue(7);
console.log(finalResult());

The result is: 6

Nothing is new here. We simply want to process a value twice; the first process adds 5 to the number. Then, the value enters another process that divides (value + 5) over 2. The resultant value is returned.

Task 10: Refer to task 9; when does the variable process1Result die?

process1Result dies when the function processValue finishes execution.

Task 11: Refer to task 9; can you access process1Result within processValueAgain function?

function processValue(value){
  var process1Result = value + 5;

  function processValueAgain(){
     console.log(process1Result);
     var process2Result = process1Result / 2;

     return process2Result;
  }

  return processValueAgain;
}

var finalResult = processValue(7);
console.log(finalResult());

The result is as follows:

12
6

As you can see, the function processValue finishes execution, which means the variable process1Result has died. However, processValueAgain function still remembers the value of process1Result. How?!

Closures

What we have created in task 3 and task 9 is a closure.

A closure is a function that remembers the variables created in its enclosing function.

A closure function can access:

  1. the variables declared within it.
  2. the variables declared in the outer function.
  3. the global variables.

Task 12: Refer to task 9; append console.dir(finalResult), and check the result.

function processValue(value){
  var process1Result = value + 5;

  function processValueAgain(){
     var process2Result = process1Result / 2;

     return process2Result;
  }

  return processValueAgain;
}

var finalResult = processValue(7);
console.log(finalResult());

console.dir(finalResult);

The result is as follows:

6
ƒ processValueAgain()

Click on the arrow on the right of ƒ processValueAgain(), you can see now:

ƒ processValueAgain()
  arguments: null
  caller: null
  length: 0
  name: "processValueAgain"
  prototype: {constructor: ƒ}
  __proto__: ƒ ()
  [[Scopes]]: Scopes[2]

Click on the arrow on the right of [[Scopes]]: Scopes[2], you can see:

[[Scopes]]: Scopes[2]
  0: Closure (processValue) {process1Result: 12}
  1: Global {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}

Great! You can see that the inner function processValueAgain has the access to two scopes; the first is the closure with process1Result value, and the global scope variables.

console.dir()

console.dir(something) gives us a list of the properties of something.

We can write console.dir(function) to get to know about the scopes that a function has the access to.

Task 13: Create a regular function that adds 1 to a variable each time the function is called. Suppose that this variable is called num, and its initial value is zero.

var num = 0;
function counter(){
  num++;
  return num;
}

console.log(counter());
console.log(counter());
console.log(counter());

The result is as follows:

1
2
3

Task 14: Refer to task 13; append num = 15 to the code, and re-call the function counter after that.

var num = 0;
function counter(){
  num++;
  return num;
}

console.log(counter());
console.log(counter());
console.log(counter());

num = 15;

console.log(counter());
console.log(counter());

The result is as follows:

1
2
3
16
17

We want to update the value of num only when the function is called. Now num is updated outside the function, this affects on the value of our counter.

Task 15: Re-do task 13; make sure that num is only updated inside the function, and it is not affected by the variables outside the function.

function counter(){
  var num = 0;
  num++;
  return num;
}

console.log(counter());
console.log(counter());
console.log(counter());

The result is as follows:

1
1
1

Well! The variable num is always 1. Why? because whenever we call the function, num is set to 0 again.

Task 16: Solve the issue introduced in task 15 using closures.

function counter(){
  var num = 0;
  function updateNum(){
    num++;
    return num;
  }
  return updateNum;
}

console.log(counter()());
console.log(counter()());
console.log(counter()());

The result is as follows:

1
1
1

We still have the same problem here. Every time we call the function, num is reset to zero. We need to execute the outer function counter only once, in such way num will be set to zero only once.

Task 17: Solve the issue introduced in task 16 using IIFE.

var counter = (function(){
  var num = 0;
  function updateNum(){
    num++;
    return num;
  }
  return updateNum;
})();

console.log(counter());
console.log(counter());
console.log(counter());

The result is as follows:

1
2
3

Note

In task 17; num is a private variable, it can not be changed outside the function. It is only accessible for the inner function. The inner function updateNum can also update the value of the outer function variables.

The tasks 13-17 are inspired from this Stack overflow question

Task 18: Create a function that takes a number. It returns another function. The inner function takes another number and returns the multiplication of the first and the second number.

function func1(num1){

  function func2(num2){
    return num1 * num2;
  }

  return func2;
}

console.log(func1(2)(4));
console.log(func1(5)(10));
console.log(func1(1)(0));
console.log(func1(15)(8));

The result is as follows:

8
50
0
120

Task 19: Refer to task 18; which scopes func2 has access to? Hint: use console.dir(func1()).

function func1(num1){

  function func2(num2){
    return num1 * num2;
  }

  return func2;
}

console.dir(func1());

The result is as follows:

0: Closure (func1) {num1: undefined}
1: Global {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}

num1 is not specified yet. Therefore, num1 is undefined.

func2 has access to two scopes; the first is the outer function scope func1, and the second is the global scope.

Lexical Scope

Lexical scope of a function is where that function was defined. In the lexical scope; the function can access its own local variables, the parent function variables and the global variables.

Task 20: Refer to task 18; create func3 inside func2 that takes a third number. func3 returns num1 * num2 + num3. func2 returns func3.

function func1(num1){

  function func2(num2){

    function func3(num3){
      return num1 * num2 + num3;
    }

    return func3;
  }

  return func2;
}

console.log(func1(2)(4)(8));
console.log(func1(5)(10)(6));
console.log(func1(1)(0)(1));
console.log(func1(15)(8)(10));

The result is as follows:

16
56
1
130

Task 21: Refer to task 20; which scopes func3 has access to? Hint: use console.dir(func1()()).

function func1(num1){

  function func2(num2){

    function func3(num3){
      return num1 * num2 + num3;
    }

    return func3;
  }

  return func2;
}

console.dir(func1()())

The result is as follows:

0: Closure (func2) {num2: undefined}
1: Closure (func1) {num1: undefined}
2: Global {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}

func3 has access access to the func2 variables, func1 variables, and the global scope variables.

Closures Again

A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created. See Closures