A closure is a function that retains access to variables from an outer (enclosing) function's scope, even after the outer function has finished executing. It creates a persistent bridge between a function's inner and outer lexical environments.
Key Characteristics: Closures prevent the garbage collector from reclaiming variables from their outer scope. When accessing a free variable (a variable not declared locally), the function looks within the lexical scope where it was created, not necessarily its direct parent execution context.
Potential Drawback: Since closed-over variables are not garbage collected as long as the closure exists, improper use can lead to increased memory consumption, often described as a memory leak.
Identifying Patterns: Closures typically occur in two scenarios: when a function is returned from another function, or when a function is passed as an argument to another function.
Practical Implementations:
-
Function Debouncing
const createDebouncer = (executable, delay) => { let timerId; return (...args) => { clearTimeout(timerId); timerId = setTimeout(() => { executable.apply(null, args); }, delay); }; }; const logScroll = () => { console.log('Scroll event processed.'); }; window.addEventListener('scroll', createDebouncer(logScroll, 1000)); -
Encapsulating Private Variables
const UserAccount = function() { let accountBalance = 0; // Private variable this.getBalance = function() { return accountBalance; }; this.deposit = function(amount) { accountBalance += amount; }; }; UserAccount.prototype = { tryToAccessBalance() { return this.accountBalance; // Will not work } }; const user = new UserAccount(); user.deposit(100); console.log(user.getBalance()); // 100 console.log(user.accountBalance); // undefined console.log(user.tryToAccessBalance()); // undefined -
Preserving Loop State with IIFE Closures A common issue occurs when a loop variable is used inside an asynchronous callback, like
setTimeout.// Problem: All callbacks log the final value of `i`. for (var index = 0; index < 5; index++) { setTimeout(() => console.log(index), 100); } // Output: 5, 5, 5, 5, 5Solution using an Immediately Invoked Function Expression (IIFE) to create a new scope for each iteration:
for (var index = 0; index < 5; index++) { (function(capturedIndex) { setTimeout(() => console.log(capturedIndex), 100); })(index); } // Output: 0, 1, 2, 3, 4The IIFE creates a new function scope for each iteration, capturing the current value of
indexin thecapturedIndexparameter, which is then accessible to the innersetTimeoutcallback.