Lexical Scope and Closures are important concepts in JavaScript that help you understand how variables are accessed, stored, and remembered, especially in nested functions.
Lexical scope refers to determining the scope of variables and functions at compile time, based on where they are declared within the code.
When you define a function inside another function, the inner function can see and use the variables of the outer function but the outer function can’t see and use the variables of the inner function.
A Closure is created when an inner function has access to its outer function’s (parent scope) variables, even after the outer function (parent function) has finished executing.
Let's understand both with this example:
function outerFunc() {
let outerVar = "I am a variable inside outer function!";
function innerFunc() {
console.log(outerVar);
}
return innerFunc;
}
const closureFunc = outerFunc();
closureFunc(); // Output: I am a variable inside outer function!
Here,
- You have a function
outerFunc
and inside this function, you have a variableouterVar
. - Inside the
outerFunc
function, there’s another functioninnerFunc
and thisinnerFunc
can access theouterVar
variable because it’s defined in the outer function. This connection is possible due to lexical scope, which allows inner functions to reach out and use variables from their outer functions. - When you call
outerFunc
then it will return theinnerFunc
. - Then, you are storing the result of
outerFunc()
toclosureFunc
variable byconst closureFunc = outerFunc();
. - Now,
closureFunc
is essentiallyinnerFunc
because when you callouterFunc
then it returns theinnerFunc
. - But
closureFunc
remembers the environment in which it was created and has access toouterVar
. - Finally, on
closureFunc()
, you’re outside the originalouterFunc
but it still knows aboutouterVar
. So, the output is:I am a variable inside outer function!
.
Think of lexical scope like a set of rules that determine where in your code variables and functions live. If a function is inside another function, the inner function can use the outer function’s stuff, but the outer function can’t use the inner function’s stuff.
A closure is like a memory superpower for functions. Even after a function is done doing its thing, if it had an inner function, that inner function can still remember and use the things from its parent function.
A closure exists because of lexical scope. Lexical scope defines what a function has access to, and closures take advantage of that to remember those variables even after the outer function has finished.
Practical Use Case
Here's a practical example showing how closures can maintain state:
function createCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter1 = createCounter();
const counter2 = createCounter();
console.log(counter1()); // Output: 1
console.log(counter1()); // Output: 2
console.log(counter2()); // Output: 1 (separate counter)
console.log(counter1()); // Output: 3
Here,
createCounter
is a function that makes and returns another function.- Inside it, there's a private variable
count
set to0
. - The returned function increases
count
and gives back the new value. - This inner function remembers
count
, even thoughcreateCounter()
is done running, that's a closure in action. counter1
gets its own count starting from 0.counter2
also gets a fresh count, separate fromcounter1
.counter1()
runs and increases its own count:1 → 2 → 3
counter2()
is a different counter. It starts fresh at 1.- So both
counter1
andcounter2
have their own separate state, thanks to closures. - Every time you call
createCounter()
, it creates a new closure with its own count. - The inner function remembers the variable from the outer function, this is how closures work.
- Closures help you keep variables private and independent from others.
Why Closures Are Useful
- Private Variables: Closures let you create variables that can’t be seen or changed from outside the function.
- Custom Functions: You can build functions that remember certain values and behave in a specific way.
- Event Handling: Closures help your code remember things when working with events or delayed actions.
- Data Hiding in Modules: They help keep some data hidden and organized when building parts of your app.
Things to Keep in Mind
While closures are powerful, there are some things to keep in mind:
- Memory Use: Closures hold on to variables they use, which can stop those variables from being deleted. If not handled well, this can use more memory than needed.
- Performance: Creating many closures can impact performance, especially in loops or frequently called functions.