A closure is the combination of a function and the lexical environment within which that function was declared.
OR
A closure in JavaScript is a function that remembers its lexical scope (the scope in which it was created) even when it is executed outside that scope. This means that a function can "remember" the environment in which it was created, including any variables that were in scope at the time.
Imagine you want to speed posting some documents. You put all the documents in an envelope and sealed it. All the documents are inside the envelope and after speed posting from one place to another, the documents are still inside it. Similarly, a closure "remembers" the variables from the place where it was created, even after it moves to a different place in the code.
Lexical Scoping:
JavaScript functions are lexically scoped, meaning that they can access variables from the surrounding code in which they were defined, even if that surrounding code is no longer in execution.
In simple terms:
- Lexical scope means that the scope of a variable is defined by where it is written in the code, not where it is called from.
- A closure captures variables from the outer scope at the time the function is created, not when it is called.
How does closure work?
In a closure, when a function is declared inside another function, it gets access to the outer function's variables. Even after the outer function has finished executing, the inner function retains access to those variables.
1: Basic Closure Example:
function outerFunction() {
let outerVariable = 'I am outside!';
function innerFunction() {
console.log(outerVariable); // Inner function uses outerVariable
}
return innerFunction;
}
const closureFunc = outerFunction(); // Call the outer function
closureFunc(); // Even though outerFunction is done, innerFunction can still access outerVariable
// Output: I am outside!
Explanation:
-
outerFunctionhas a variable calledouterVariableand it returns another function (calledinnerFunction). - When
outerFunction()is called, it returns theinnerFunction. - Even though
outerFunctionhas finished executing by the timeclosureFuncis called, theinnerFunctionstill remembers theouterVariablethat was declared in the outer function. This is theclosure.
2: Closure with Arguments:
function multiplier(x) {
return function(y) {
return x * y;
}
}
const multiplyBy = multiplier(5);
console.log(multiplyBy(10)); // Output: 50
console.log(multiplyBy(6)); // Output: 30
Explanation:
-
multiplieris a function that takes a parameter x and returns an inner function. - The inner function takes a parameter y and returns the multiply of x and y.
- When we call
multiplier(5), it returns a new function that multiplies 5 by any number passed into it. - Each call to
multiplyByremembers x = 5 (from the outer scope) and can access it to calculate the result.
3: Closures and SetTimeout
function createCounter() {
let count = 0;
function innerCounter() {
count++;
console.log(count);
}
return innerCounter;
}
const counter = createCounter();
counter(); // Immedietely Output 1
setTimeout(counter, 1000); // Output after 1 second: 2
setTimeout(counter, 2000); // Output after 2 seconds: 3
setTimeout(counter, 3000); // Output after 3 seconds: 4
Explanation:
- The function
createCounterdefines a variablecountand returns a functioninnerCounterthat incrementscounteach time it is called. - Even though
createCounterhas finished executing, the returned function (the closure) still "remembers"count. - The
setTimeoutcalls invoke the returned function, and the counter continues to increment as expected.
4: Practical Use of Closure (Data Encapsulation)
function Counter() {
let count = 0; // private variable
this.increment = function() {
count++;
console.log(count);
}
this.decrement = function() {
count--;
console.log(count);
}
this.getCount = function() {
return count;
}
}
const myCounter = new Counter();
myCounter.increment(); // Output: 1
myCounter.increment(); // Output: 2
myCounter.decrement(); // Output: 1
console.log(myCounter.getCount()); // Output: 1
Explanation:
- The
Counterfunction acts as a constructor, defining a private variablecount. - The methods
increment,decrement, andgetCountform closures because they have access to thecountvariable. - Outside code cannot directly access or modify
count; it can only interact with it via the provided methods.
Why Closures are Important?
- Data Privacy/Encapsulation: You can create private variables that cannot be accessed directly from the outside world, but can still be manipulated via functions (like in the Counter example above).
- Function Factories: Closures allow you to create customized versions of a function, like the makeAdder example.
- Asynchronous Programming: Closures are useful in scenarios like asynchronous code, where functions need to "remember" their environment (like the setTimeout example).
Performance Consideration
While closures are a powerful tool, they come with some potential performance implications:
- Memory Usage: A closure can keep a reference to its outer variables, potentially leading to higher memory usage. If you create many closures that capture large amounts of data, it could slow down the application.
- Garbage Collection: Closures may delay the garbage collection of variables they capture. If you no longer need the closure, ensure you release references to it.
Key Concept:
- A closure is created when a function has access to its parent function’s variables, even after the parent function has finished executing.
- The inner function "closes over" the variables from the outer function, which is why it’s called a closure.
Top comments (1)
Nice