Search This Blog

2024/03/24

Javascript : Closure

A closure is the combination of a function bundled together (enclosed)
with references to its surrounding state (the lexical environment)


Code:
function outerFunction() {
const outerVariable = "I am outer!";
function innerFunction() {
var innerVar = "I am inner";
//inner function has access to outer function variables
console.log(outerVariable);
}
//console.log(innerVar) //this result in error as it is not defined,
//hence outer function do not have access to inner function variables
return innerFunction;
}
var outer = outerFunction()
outer()

Output:
I am outer!

Explanation:
In context of closure
1) Inner function has access to variables defined in outer function even after that function has returned.
2) Outer function do not have access to variables defined in inner function.

Using Closure to create private method:

Languages such as Java allow you to declare methods as private,
meaning that they can be called only by other methods in the
same class.

JavaScript, prior to classes, didn't have a native way of declaring
private methods, but it was possible to emulate private methods using
closures. Private methods aren't just useful for restricting access
to code. They also provide a powerful way of managing your global namespace.

Code:
const counter = (function () {
let privateCounter = 0;//private variable
//private function
function changeBy(val) {
privateCounter += val;
}
return {
increment() {
changeBy(1);
},
decrement() {
changeBy(-1);
},
value() {
return privateCounter;
},
};
})();
console.log(counter.value()); // 0.
counter.increment();//set privateCounter variable value to 1
counter.increment();//set privateCounter variable value to 2
console.log(counter.value()); // 2
counter.decrement();//set privateCounter variable value to 1
console.log(counter.value()); // 1.

Output:
0
2
1

Explanation:
Single lexical environment that is shared by the three functions: counter.increment, counter.decrement, and counter.value.
This lexical environment contain two private items namely variable privateCounter & changeBy function.

You can't access either of these private members from outside the anonymous function.
Instead, you can access them using the three public functions that are returned from
the anonymous wrapper.

Variants of closure:
There are two common ways in which closure can be implemented.

1) Closure returning function:

Code:

function greetPersonCreator() {
const message = 'Hello';
function displayMessage(name) {
console.log(`${message} ${name}!`);
}
return displayMessage;
}
const greetPerson = greetPersonCreator();
console.log(typeof(greetPerson)); // function
greetPerson('Alex'); // Hello Alex!

Output:
function
Hello Alex!
2) Closure not returning function:
Code:

function greetPerson(name) {
const message = 'Hello';
function displayMessage() {
console.log(`${message} ${name}!`);
}
displayMessage();
}
greetPerson('Alex'); // Hello Alex!

Output:
Hello Alex!
3) Closure can also return an object.
Code:
const makeCounter = function () {
let privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment() {
changeBy(1);
},
decrement() {
changeBy(-1);
},
value() {
return privateCounter;
},
};
};
const counter1 = makeCounter();
const counter2 = makeCounter();
console.log(counter1.value()); // 0.
counter1.increment();
counter1.increment();
console.log(counter1.value()); // 2.
counter1.decrement();
console.log(counter1.value()); // 1.
console.log(counter2.value()); // 0.

Output:
0
2
1
0

Independent lexical environment for different variables just like two distinct object created from a class in OOP.

Code:
const makeCounter = function () {
let privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment() {
changeBy(1);
},
decrement() {
changeBy(-1);
},
value() {
return privateCounter;
},
};
};
const counter1 = makeCounter();
const counter2 = makeCounter();
console.log(counter1.value()); // 0.
counter1.increment();
counter1.increment();
console.log(counter1.value()); // 2.
counter1.decrement();
console.log(counter1.value()); // 1.
console.log(counter2.value()); // 0.

Output:
0
2
1
0

Explanation:
Two counters namely counter1 & counter2 maintain their independence from one another.
Each closure references a different version of the privateCounter variable through its
own closure.
Each time one of the counters is called, its lexical environment changes by changing
the value of this variable. Changes to the variable value in one closure don't affect
the value in the other closure.

Encapsulation (Information Hiding):

Encapsulation is the practice of bundling related data into a structured unit,
along with the methods used to work with that data.The OOP languages implement
encapsulation through classes and the objects instantiated through those classes.

a class hides the implementation details of the programmed elements, while restricting
direct access to those elements. It also provides a public interface for interacting
with the class through its instantiated objects.

Encapsulation hides an object’s internal representation or state from the outside.

In previous implementation of closure variable privateCounter & method
changeBy are hidden ,implementation of changeBy is also hidden.These two elements can only be accessed through
provided methods namely increment,decrement,value.This is nothing but Encapsulation.

Creating Function Factory using closure:
Closures can be used to create factory functions that generate specialized functions or objects
with specific behavior.

Code:
function createMultiplier(factor) {
// The outer function takes a 'factor' argument and returns an inner function.
// The inner function is the actual function that will perform multiplication.
return function (number) {
// The inner function has access to the 'factor' variable from the outer function's scope.
return number * factor;
};
}
// Create specific multiplier functions using the factory.
const double = createMultiplier(2);
const triple = createMultiplier(3);
// Use the generated functions.
console.log(double(5)); // 10
console.log(triple(5)); // 15

Output:
10
15

Module Pattern with closure:
The module pattern is a design pattern used for improving the
maintainability and reusability of the code by creating public
and private access levels.

It protects the pieces from the global scope, avoiding possible
errors and conflicts.

The module pattern is quite similar to an IIFE (immediately invoked functional expression).
A module always returns an object instead of a function.

Code:
const createSupplier = (function () {
//private variables
const name = "General Motors";
const field = "automobile";
//private method
const getSupplierName = () => name;
return {
name,
field,
getName: () => getSupplierName(),
};
})();
console.log("Supplier Name:" + createSupplier.name);
console.log("Field:" + createSupplier.field);
console.log("Supplier Name:"+ createSupplier.getName());

Output:
Supplier Name:General Motors
Field:automobile
Supplier Name:General Motors

Exception in closures:

There is just one exeption in closures. In JavaScript every
function is a closure except for functions created via "new Function" syntax.

Usually a function have access to the lexical environment where it was created.
But when a function is created via the "new Function" syntax, it doesn't have
access to the lexical environment but the global one.

function getFunc() {
let value = "test";
let func = new Function("console.log(value)");
return func;
}
getFunc()(); // error: value is not defined
Output:
we get an error stating
ReferenceError: value is not defined

Explanation:
Here 'value' variable is defined in function getFunc(),we are not having
any parameters for function created with new Function constructor.
If console.log() inside it got variable from outer function 'getFunc()'
then it would have printed output as
test
but it isn't.hence this type of functions cant have access to variables
from outer function.


Implementing Callback function using closure:

In JavaScript, a callback function is a function that is passed as
an argument to another function and is executed by that function.

The callback function can access variables in its outer scope,
including variables defined in the function that calls it.

Code:


function fetchData(url, callback) {
fetch(url)
.then((response) => response.json())
.then((data) => callback(data))
.catch((error) => console.error(error));
}
//callback
function processData(data) {
console.log(data);
}
fetchData("https://jsonplaceholder.typicode.com/todos/1", processData);

Output:

Explanation:
The callback function 'processData()' has access to the data variable in its outer scope,
which is the result of the fetchData function.

As callbacks behave as if they are actually placed inside that
function, they are in practice closures: they can access the
containing function’s variables and parameters, and even
the variables from the global scope.

Another Example:

var processData = function (msg, callback) {
console.log("Doing some tasks");
callback(msg);
};
var myCallback = function (myMsg) {
console.log(myMsg);
};
processData("hello", myCallback);
processData("world", myCallback);

Output:
Doing some tasks
hello
Doing some tasks
world


Explanation: we have created a simple 'myCallback()' function in outer scope.
we are passing it to processData() function as a callback (function as parameter).

In 'processData()' function calls 'callback()' function,assuming 'callback()'
defined inside it as inner function it get access to 'msg' variable due to closure

No comments:

Post a Comment