The concept of closures in JavaScript, using the code examples from closures.js
.
function outerFunction() {
let outerVariable = 'I am from outer!';
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
const closure1 = outerFunction();
closure1(); // Output: I am from outer!
Explanation:
innerFunction
forms a closure over outerVariable
from outerFunction
.outerFunction
finishes, innerFunction
can access outerVariable
.function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3
Explanation:
count
is private to the returned function, not accessible from outside.counter()
increments and returns the private count
.Problem:
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs.push(function() {
console.log('Problem:', i);
});
}
funcs[0](); // 3
funcs[1](); // 3
funcs[2](); // 3
Solution 1: IIFE
var funcsFixed = [];
for (var j = 0; j < 3; j++) {
(function(jCopy) {
funcsFixed.push(function() {
console.log('Fixed:', jCopy);
});
})(j);
}
funcsFixed[0](); // 0
funcsFixed[1](); // 1
funcsFixed[2](); // 2
Solution 2: Use let
let funcsLet = [];
for (let k = 0; k < 3; k++) {
funcsLet.push(function() {
console.log('Let:', k);
});
}
funcsLet[0](); // 0
funcsLet[1](); // 1
funcsLet[2](); // 2
Explanation:
var
causes all functions to share the same variable.let
creates a new scope for each iteration.function makeMultiplier(multiplier) {
return function(x) {
return x * multiplier;
};
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
double(5); // 10
triple(5); // 15
Explanation:
makeMultiplier
returns a function that remembers the multiplier
value.Problem:
for (var m = 0; m < 3; m++) {
setTimeout(function() {
console.log('Async Problem:', m);
}, 100);
}
// All log 3
Solution:
for (let n = 0; n < 3; n++) {
setTimeout(function() {
console.log('Async Fixed:', n);
}, 200);
}
// Logs 0, 1, 2
Explanation:
let
creates a new binding for each iteration, so closures capture the correct value.function Person(name) {
let _name = name;
return {
getName: function() { return _name; },
setName: function(newName) { _name = newName; }
};
}
const p = Person('Alice');
p.getName(); // Alice
p.setName('Bob');
p.getName(); // Bob
Explanation:
_name
is private, only accessible via getName
and setName
.A closure is a function that remembers its lexical scope even when the function is executed outside that scope. It allows functions to access variables from an enclosing scope, even after that scope has closed.
function add(a) {
return function(b) {
return a + b;
};
}
const add5 = add(5);
add5(10); // 15
Explanation:
add
returns a function that remembers a
.Be careful: Closures can cause memory leaks if they retain references to large objects unnecessarily. Always clean up references if not needed.
Example:
function createLeak() {
let largeArray = new Array(1e6).fill('leak'); // Large object
return function() {
// This closure keeps largeArray in memory
console.log('Still holding largeArray of length:', largeArray.length);
};
}
let leaky = createLeak();
// Even if we don't need largeArray anymore, it's not garbage collected
// because the closure (leaky) still references it.
// To avoid the leak, set leaky = null when done:
// leaky = null;
Closures are a powerful feature in JavaScript, enabling data privacy, function factories, and more. Understanding closures is essential for interviews and real-world development.
This section explains how closures interact with setTimeout
and loops, a common source of confusion in JavaScript.
var
in Loopsfunction y() {
for (var i = 1; i <= 5; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
}
y();
What happens?
6
five times (after 1s, 2s, …, 5s).var
is function-scoped, not block-scoped. By the time the callbacks run, the loop is done and i
is 6
.function z() {
for (var i = 1; i <= 5; i++) {
(function(closeI) {
setTimeout(() => {
console.log(closeI);
}, closeI * 1000);
})(i);
}
}
z();
How does this work?
i
as closeI
.1, 2, 3, 4, 5
as expected.let
function a() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
}
a();
How does this work?
let
is block-scoped, so each iteration gets a new binding of i
.i
value.0, 1, 2, 3, 4
as expected.var
, all callbacks share the same variable, leading to unexpected results in async code.let
or an IIFE to capture the correct value for each iteration.Summary:
When using asynchronous functions like
setTimeout
inside loops, always be mindful of variable scope. Uselet
or an IIFE to avoid common pitfalls with closures andvar
.
Practice: Try modifying the examples in closures.js
& setTimeOutPractice.js
and observe the results to deepen your understanding.