Day 15: Understanding Closures
Screenshot:-
Closures are a fundamental concept in JavaScript that allows functions to access variables from an enclosing scope. This can be a bit tricky to grasp at first, but let's break it down with simple examples to make it easier to understand.
Activity 1: Understanding Closures
Task 1: A Function Returning Another Function
Here's an example of a function that returns another function. The inner function can access a variable from the outer function's scope:
function outerfun() {
let num = 3;
function innerfunc() {
return num;
}
return innerfunc;
}
const print = outerfun();
console.log(print()); // Output: 3
In this example, innerfunc
is a closure. It "closes over" the variable num
from the outer function outerfun
, allowing it to access num
even after outerfun
has finished executing.
Task 2: A Closure with a Private Counter
Closures can be used to create private variables. Here’s an example of a closure that maintains a private counter:
function createcounter() {
let counter = 0;
return {
increment: function () {
counter++;
},
getvalue: function () {
return counter;
},
};
}
let counter = createcounter();
counter.increment();
console.log(counter.getvalue()); // Output: 1
counter.increment();
console.log(counter.getvalue()); // Output: 2
In this example, the counter
variable is private to the createcounter
function. The only way to access or modify counter
is through the returned increment
and getvalue
methods.
Activity 2: Practical Closures
Task 3: Generating Unique IDs
Closures can be used to generate unique IDs by keeping track of the last generated ID:
function generateId() {
let lastid = 0;
return function () {
lastid++;
return lastid;
};
}
const generated = generateId();
console.log(generated()); // Output: 1
console.log(generated()); // Output: 2
console.log(generated()); // Output: 3
Here, the lastid
variable is private to the generateId
function, and each time the returned function is called, lastid
is incremented and returned.
Task 4: Greeting a User by Name
Closures can capture and remember values. Here’s an example where we capture a username:
function takeusername(username) {
let uname = username;
return function () {
return uname;
};
}
const myname = takeusername("Alpit");
console.log(myname()); // Output: Alpit
In this example, uname
is captured by the returned function, allowing it to remember and return the username.
Activity 3: Closures in Loops
Task 5: Ensuring Functions Log Correct Index in Loops
Closures can be used to ensure that functions created in a loop log the correct index:
function createfunctionarray() {
let functionArray = [];
for (let i = 0; i < 5; i++) {
functionArray.push((function(index) {
return function () {
console.log(index);
};
})(i));
}
return functionArray;
}
const functions = createfunctionarray();
functions[0](); // Output: 0
functions[1](); // Output: 1
functions[2](); // Output: 2
functions[3](); // Output: 3
functions[4](); // Output: 4
In this example, we use an immediately invoked function expression (IIFE) to create a new scope for each function, capturing the current value of i
.
Activity 4: Module Pattern
Task 6: Managing a Collection of Items
Closures can be used to create modules, which are self-contained pieces of code. Here’s an example of a simple item manager module:
const itemManager = (function () {
let items = [];
return {
addItem: function (item) {
items.push(item);
console.log(`Added: ${item}`);
},
removeItem: function (item) {
const index = items.indexOf(item);
if (index > -1) {
items.splice(index, 1);
console.log(`Removed: ${item}`);
}
},
listItems: function () {
return items;
},
};
})();
itemManager.addItem("Apple");
itemManager.addItem("Banana");
itemManager.removeItem("Apple");
console.log(itemManager.listItems()); // Output: ["Banana"]
In this example, the items
array is private to the item manager module. The only way to interact with items
is through the provided methods.
Activity 5: Memoization
Task 7: Memoizing Function Results
Memoization is an optimization technique that stores the results of expensive function calls and returns the cached result when the same inputs occur again:
function memoize(fn) {
const cache = {};
return function (...args) {
const key = JSON.stringify(args);
if (cache[key]) {
return cache[key];
}
const result = fn(...args);
cache[key] = result;
return result;
};
}
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
const memoizedFactorial = memoize(factorial);
console.log(memoizedFactorial(5)); // Output: 120
console.log(memoizedFactorial(5)); // Output: 120 (cached result)
In this example, memoize
is a higher-order function that returns a memoized version of the factorial
function.
By mastering closures, you can write more efficient and modular JavaScript code. Closures allow you to create private variables, encapsulate functionality, and optimize performance with techniques like memoization. Happy coding!
Subscribe to my newsletter
Read articles from Alpit Kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Alpit Kumar
Alpit Kumar
I am a passionate web developer and open-source enthusiast on a captivating journey of coding wonders. With a year of experience in web development, my curiosity led me to the enchanting world of React, where I found a true calling. Embracing the magic of collaboration and knowledge-sharing, I ventured into the realm of open source, contributing to Digital Public Goods (DPGs) for the betterment of the digital universe. A firm believer in learning in public, I share my insights and discoveries through blogging, inspiring fellow coders to embark on their own magical coding odysseys. Join me on this thrilling adventure, where imagination and technology converge, and together, let's shape the future of the digital landscape! 🎩✨