In JavaScript, a closure is a way for a function to remember and use variables from its outer function and global variables, even after that function has finished executing. It is really helpful for things like keeping data private or creating functions that work together. This blog discusses the basic JavaScript Closure concept, scoping of variables, nested functions, and other related concepts that help you understand them with examples.
Table of Contents:
What are JavaScript Closures?
A closure in JavaScript is when a function “remembers” the variables from the place where it was created, even after the function that created it has finished running. This means the closure can still access variables from its outer function, even though that outer function has already completed execution.
Closures are used to create private variables in functions. Since JavaScript doesn’t have built-in support for private variables, closures can contain variables, making them accessible only to certain functions.
Example: This example shows the basic creation and use of JavaScript closures.
Output:
Here,
- The outerFunction is defined with one parameter outerVariable. Inside outerFunction, an innerFunction is defined and returned.
- The innerFunction takes one parameter innerVariable. The innerFunction has access to both outerVariable and innerVariable because of the closure.
- When outerFunction is called with ‘outside’, it returns innerFunction.
- The newFunction now holds the innerFunction returned by outerFunction, and when newFunction is called with ‘inside’, it logs both outerVariable and innerVariable.
Scoping in Closures
Scope defines the accessibility and visibility of the variable and functions in different parts of the code. There are 2 types of scopes, which are local and global.
- Global Scope defines the scope that exists outside of all functions. Variables defined in this scope are accessible from anywhere in the program.
- Local Scope defines the scope within a function. Variables declared inside a function are only accessible within that function, not outside.
Example: This example explains the Scoping in JavaScript closures.
Output:
Get 100% Hike!
Master Most in Demand Skills Now!
Lexical Scoping and Execution Context
In JavaScript, lexical scoping and execution context play an important role in defining how variables and functions are accessed and executed.
1. Lexical Scoping: Lexical scoping means that the scope of a variable is defined by its position in the source code. In simpler words, a function’s scope is defined by where it is written in the code, rather than where it is called.
How Lexical Scoping Works:
- JavaScript uses block-level scope for let and const and function-level scope for var.
- Inner functions can access variables from their outer functions because of lexical scoping.
2. Execution Context: Execution context is like a box where JavaScript runs the code. It defines the environment where the current code is being executed.
There are two main types of execution context:
- Global execution context (GEC): It is created when JavaScript starts running.
- Function execution context (FEC): It is created when a function is called.
Phases of Execution:
When JavaScript runs a code, it does two things:
- Creation phase (memory setup):
- JavaScript sets up memory for variables and functions.
- Variables that are declared with var start with undefined because of hoisting.
- Execution phase (running the code):
- It assigns values to variables and runs the functions.
How Lexical Scoping and Execution Context Work Together?
- Every function remembers where it was written, which is the lexical scope.
- When the function runs, JavaScript creates an execution context for it.
- If a function needs a variable, it first looks inside itself, then in its parent function, and then in the global scope.
Example:
Output:
Practical Uses of JavaScript Closures
Closures are very useful in real-world JavaScript programming. They help functions to remember variables even after the outer function has already finished running. Let’s see some practical uses of closures.
1. Data Hiding (Encapsulation)
Closures allow you to hide variables inside a function so they can’t be accessed directly from outside.
Example: Counter with Private Data
Output:
Explanation:
- The count variable is protected from outside changes.
- You can only update it using increment and decrement.
2. Avoiding Repeated Function Calls (Memoization)
Closures can store previous results and return them when needed, which helps in improving performance.
Example: Caching Fibonacci Results
Output:
Explanation:
- It saves time by storing previous calculations instead of recalculating them.
3. Fixing setTimeout Inside Loops
Closures help you avoid common bugs when you are using setTimeout inside loops.
Example: Correcting setTimeout Loop Issue
Output:
Explanation:
- It makes sure each setTimeout function remembers the correct ‘i’ value.
- Without the closure, all logs will print 3 because of var scoping problems.
4. Creating Functions Dynamically (Function Factories)
Closures allow you to create multiple functions with different values.
Example: Multiplication Functions
Output:
Explanation:
- It helps you create custom functions without repeating logic.
Common Problems with JavaScript Closures
Closures are very useful, but sometimes they create unexpected behaviour and performance issues. Let’s see some common problems and how you can fix them:
1. Accidentally creating global variables: If you forget to declare let or const, then JavaScript will create a global variable without warning. This can create problems in other parts of your code before you realize it.
How to Fix:
You should always declare your variables using let or const inside functions to keep them properly scoped.
2. setTimeout inside a loop not working as expected: If you use var inside a loop with setTimeout, then all timers will use the last value of the variable instead of different values. It happens because var does not create a new variable for each loop cycle.
How to Fix:
You should use let instead of var or put the variable in a separate function so that each timer remembers its own value.
3. Holding onto unused data (memory leaks): Closures always keep variables in memory, even if they are no longer needed. If the stored data is too large, then it will slow your program.
How to Fix:
You should set variables to null when they are no longer needed to free up memory.
4. Unexpected behaviour in loops: When you create multiple functions inside a loop, they might all reference the same variable, which will lead to wrong results.
How to Fix:
You should use let in the loop or put the variable inside a function so each function gets a new copy of the value.
5. Event listeners running too many times: If closure is used in an event listener and added multiple times, then the function will run multiple times unexpectedly.
How to Fix?
You should remove old event listeners before adding new ones, or you have to make sure the listener is only added once.
6. Closures preventing elements from being removed: If a closure keeps a reference to a DOM element even after the element is removed from the page, then the browser will not delete it from memory. This can create performance issues and can also slow your webpage.
How to Fix?
You should remove event listeners or clear references when elements are no longer needed.
Advantages of JavaScript Closures
- Data Privacy: Variables inside a closure are hidden, and you can not access them from outside.
- Private Methods: It helps you to create functions that only the closure can access.
- Memory Efficiency: It remembers the state without using global variables.
- Modularity: It helps you organize code and avoid crashing variables.
- Currying & Partial Application: It helps you create reusable and flexible functions.
Interesting Things About JavaScript Closures
- Closures remember variables: Even after a function has finished running, a closure still can access its outer function’s variables and can use them later.
- Help create private variables: JavaScript does not have built-in private variables, but closures allow you to create hidden data that you can not access from outside.
- Important for callbacks: Many JavaScript features, like event listeners and asynchronous functions, use closures to remember values even after the function has already stopped running.
- Useful in functional programming: Closures make functions more reusable by allowing techniques like currying that break functions into smaller parts.
- Help in organizing code: Many JavaScript frameworks use closures to store private methods and variables that help keep the code clean and safe.
- It can cause memory issues: If not handled properly, closures can keep unnecessary data in memory, which can slow your program.
- Fix loop issues: Closures help you store different values correctly in loops, like when you use setTimeout, which can return the wrong results.
- Used in popular JavaScript frameworks: Frameworks like React, Angular, and Vue use closures to manage data, control event listeners, and improve performance.
Debugging Closures in JavaScript
- Use console.log(): Print variables inside closures to track their values and changes.
- Check scope in DevTools: Use the browser’s debugger to find which variables are stored in the closure.
- Use debugger statement: Pause execution and check the code to find issues.
- Fix loop issues: Use let instead of var or wrap loops in a function to prevent closures from capturing the wrong values.
- Check for memory leaks: Track memory usage in DevTools to make sure that closures are not holding unnecessary data.
Be careful with asynchronous code: Make sure that closures store the correct values when used in setTimeout, event listeners, or API calls.
Conclusion
So far in this blog, we have learned that closures in JavaScript are a very important concept that enables functions to “remember” their lexical environment, which allows for data encapsulation, private variables, and flexible asynchronous programming. By understanding lexical scoping, execution contexts, and the scope chain concepts, we can effectively implement closure that helps to manage the state and create clearer and organized code.