6.1 What Closures Are
Closures are one of the most powerful and important concepts in JavaScript. They let functions remember their lexical environment even after they've been executed. In this lecture, we're gonna dive into what closures are all about, their quirks, and give various examples of how you can use them.
A closure in JavaScript is basically a combination of a function and the lexical environment in which the function was declared. A closure allows a function to "remember" and access variables and other functions within its outer scope even after the outer function has finished running.
Main properties of closures:
- Lexical Environment: The context where the function was declared, including all variables available at declaration time.
- Context Preservation: A function with a closure can preserve access to variables from its outer scope even after the outer function has finished executing.
6.2 Closure Examples
Example 1: Simple Closure
In this example, innerFunction() has access to the variable outerVariable from its outer scope even after outerFunction() has executed.
function outerFunction() {
let outerVariable = 'I am from the outer function';
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
const closure = outerFunction();
closure(); // Logs: I am from the outer function
Example 2: Counter with a Closure
In this example, the counter function keeps track of the count variable and increments it with each call.
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // Logs: 1
console.log(counter()); // Logs: 2
console.log(counter()); // Logs: 3
Example 3: Closure in a Loop
Closures are often used to preserve variable values in loops.
In this example, each function inside the array arr "remembers" the value of the variable i at the time it's created thanks to block-scoping with let:
function createArrayWithClosures() {
let arr = [];
for (let i = 0; i < 3; i++) {
arr[i] = function() {
console.log(i);
};
}
return arr;
}
const closures = createArrayWithClosures();
closures[0](); // Logs: 0
closures[1](); // Logs: 1
closures[2](); // Logs: 2
6.3 Complex Use Cases for Closures
Example 1: Partial Application
Closures let you create partially applied functions, with some arguments fixed.
In this example, the multiply() function returns a function that multiplies the given argument b by the fixed argument a.
function multiply(a) {
return function(b) {
return a * b;
};
}
const double = multiply(2);
console.log(double(5)); // Logs: 10
console.log(double(10)); // Logs: 20
Example 2: Data Hiding
Closures can be used to create private variables and methods.
In this example, the variables _name and _age are private and can only be accessed through the object's methods:
function createPerson(name, age) {
let _name = name;
let _age = age;
return {
getName: function() {
return _name;
},
getAge: function() {
return _age;
},
setName: function(newName) {
_name = newName;
},
setAge: function(newAge) {
_age = newAge;
}
};
}
const person = createPerson('John', 30);
console.log(person.getName()); // Logs: John
person.setName('Jane');
console.log(person.getName()); // Logs: Jane
console.log(person.getAge()); // Logs: 30
Example 3: Memoization
Memoization is an optimization technique where you cache function results to avoid recalculating for the same inputs.
In this example, the memoize() function uses a closure to store a cache of computed results of the fn() function:
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 slowFunction(num) {
console.log('Computing...');
return num * 2;
}
const memoizedFunction = memoize(slowFunction);
console.log(memoizedFunction(5)); // Logs: Computing... 10
console.log(memoizedFunction(5)); // Logs: 10 (result from cache)
GO TO FULL VERSION