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?
- The JavaScript engine declares and initializes the function
washingMachine
. - It assigns
undefined
toisClean
. - It starts executing the code from top to bottom.
- It assigns the return value of
washingMachine
toisClean
. - The function
washingMachine
returns "The clothes are clean now!". - 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:
- We turn the washing machine on
- The regular cleaning process starts
var startCleaning = true
. - The clothes are clean now
var cleanClothes = "The clothes are clean now!"
- But here we need to do extra cleaning, so the return value of
washingMachine
is theextraCleaning
function. - The clothes are being cleaned by the regular process, then it enters another cleaning process, which we call the extra cleaning
- 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:
- the variables declared within it.
- the variables declared in the outer function.
- 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