|
| 1 | +# JavaScript Closures: In-Depth Guide with Examples |
| 2 | +The concept of **closures** in JavaScript, using the code examples from `closures.js`. |
| 3 | + |
| 4 | +--- |
| 5 | + |
| 6 | +## 1. Basic Closure Example |
| 7 | +```js |
| 8 | +function outerFunction() { |
| 9 | + let outerVariable = 'I am from outer!'; |
| 10 | + function innerFunction() { |
| 11 | + console.log(outerVariable); |
| 12 | + } |
| 13 | + return innerFunction; |
| 14 | +} |
| 15 | +const closure1 = outerFunction(); |
| 16 | +closure1(); // Output: I am from outer! |
| 17 | +``` |
| 18 | +**Explanation:** |
| 19 | +- `innerFunction` forms a closure over `outerVariable` from `outerFunction`. |
| 20 | +- Even after `outerFunction` finishes, `innerFunction` can access `outerVariable`. |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +## 2. Closure with Private Variables (Data Encapsulation) |
| 25 | +```js |
| 26 | +function makeCounter() { |
| 27 | + let count = 0; |
| 28 | + return function() { |
| 29 | + count++; |
| 30 | + return count; |
| 31 | + }; |
| 32 | +} |
| 33 | +const counter = makeCounter(); |
| 34 | +counter(); // 1 |
| 35 | +counter(); // 2 |
| 36 | +counter(); // 3 |
| 37 | +``` |
| 38 | +**Explanation:** |
| 39 | +- `count` is private to the returned function, not accessible from outside. |
| 40 | +- Each call to `counter()` increments and returns the private `count`. |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## 3. Closures in Loops (Common Pitfall & Solution) |
| 45 | +**Problem:** |
| 46 | +```js |
| 47 | +var funcs = []; |
| 48 | +for (var i = 0; i < 3; i++) { |
| 49 | + funcs.push(function() { |
| 50 | + console.log('Problem:', i); |
| 51 | + }); |
| 52 | +} |
| 53 | +funcs[0](); // 3 |
| 54 | +funcs[1](); // 3 |
| 55 | +funcs[2](); // 3 |
| 56 | +``` |
| 57 | +**Solution 1: IIFE** |
| 58 | +```js |
| 59 | +var funcsFixed = []; |
| 60 | +for (var j = 0; j < 3; j++) { |
| 61 | + (function(jCopy) { |
| 62 | + funcsFixed.push(function() { |
| 63 | + console.log('Fixed:', jCopy); |
| 64 | + }); |
| 65 | + })(j); |
| 66 | +} |
| 67 | +funcsFixed[0](); // 0 |
| 68 | +funcsFixed[1](); // 1 |
| 69 | +funcsFixed[2](); // 2 |
| 70 | +``` |
| 71 | +**Solution 2: Use `let`** |
| 72 | +```js |
| 73 | +let funcsLet = []; |
| 74 | +for (let k = 0; k < 3; k++) { |
| 75 | + funcsLet.push(function() { |
| 76 | + console.log('Let:', k); |
| 77 | + }); |
| 78 | +} |
| 79 | +funcsLet[0](); // 0 |
| 80 | +funcsLet[1](); // 1 |
| 81 | +funcsLet[2](); // 2 |
| 82 | +``` |
| 83 | +**Explanation:** |
| 84 | +- Using `var` causes all functions to share the same variable. |
| 85 | +- IIFE or `let` creates a new scope for each iteration. |
| 86 | + |
| 87 | +--- |
| 88 | + |
| 89 | +## 4. Closures for Function Factories |
| 90 | +```js |
| 91 | +function makeMultiplier(multiplier) { |
| 92 | + return function(x) { |
| 93 | + return x * multiplier; |
| 94 | + }; |
| 95 | +} |
| 96 | +const double = makeMultiplier(2); |
| 97 | +const triple = makeMultiplier(3); |
| 98 | +double(5); // 10 |
| 99 | +triple(5); // 15 |
| 100 | +``` |
| 101 | +**Explanation:** |
| 102 | +- `makeMultiplier` returns a function that remembers the `multiplier` value. |
| 103 | + |
| 104 | +--- |
| 105 | + |
| 106 | +## 5. Closures in Asynchronous Code |
| 107 | +**Problem:** |
| 108 | +```js |
| 109 | +for (var m = 0; m < 3; m++) { |
| 110 | + setTimeout(function() { |
| 111 | + console.log('Async Problem:', m); |
| 112 | + }, 100); |
| 113 | +} |
| 114 | +// All log 3 |
| 115 | +``` |
| 116 | +**Solution:** |
| 117 | +```js |
| 118 | +for (let n = 0; n < 3; n++) { |
| 119 | + setTimeout(function() { |
| 120 | + console.log('Async Fixed:', n); |
| 121 | + }, 200); |
| 122 | +} |
| 123 | +// Logs 0, 1, 2 |
| 124 | +``` |
| 125 | +**Explanation:** |
| 126 | +- `let` creates a new binding for each iteration, so closures capture the correct value. |
| 127 | + |
| 128 | +--- |
| 129 | + |
| 130 | +## 6. Practical Example: Hiding Implementation Details |
| 131 | +```js |
| 132 | +function Person(name) { |
| 133 | + let _name = name; |
| 134 | + return { |
| 135 | + getName: function() { return _name; }, |
| 136 | + setName: function(newName) { _name = newName; } |
| 137 | + }; |
| 138 | +} |
| 139 | +const p = Person('Alice'); |
| 140 | +p.getName(); // Alice |
| 141 | +p.setName('Bob'); |
| 142 | +p.getName(); // Bob |
| 143 | +``` |
| 144 | +**Explanation:** |
| 145 | +- `_name` is private, only accessible via `getName` and `setName`. |
| 146 | +- Demonstrates data privacy using closures. |
| 147 | + |
| 148 | +--- |
| 149 | + |
| 150 | +## 7. Interview Question: What is a closure? |
| 151 | +> 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. |
| 152 | +
|
| 153 | +## 8. Interview Question: Why are closures useful? |
| 154 | +- Data privacy (private variables) |
| 155 | +- Function factories |
| 156 | +- Partial application/currying |
| 157 | +- Event handlers and callbacks |
| 158 | +- Maintaining state in async code |
| 159 | + |
| 160 | +--- |
| 161 | + |
| 162 | +## 9. Advanced: Currying with Closures |
| 163 | +```js |
| 164 | +function add(a) { |
| 165 | + return function(b) { |
| 166 | + return a + b; |
| 167 | + }; |
| 168 | +} |
| 169 | +const add5 = add(5); |
| 170 | +add5(10); // 15 |
| 171 | +``` |
| 172 | +**Explanation:** |
| 173 | +- `add` returns a function that remembers `a`. |
| 174 | +- This is the basis for currying and partial application. |
| 175 | + |
| 176 | +--- |
| 177 | + |
| 178 | + |
| 179 | +## 10. Memory Leaks and Closures |
| 180 | +> Be careful: Closures can cause memory leaks if they retain references to large objects unnecessarily. Always clean up references if not needed. |
| 181 | +
|
| 182 | +**Example:** |
| 183 | +```js |
| 184 | +function createLeak() { |
| 185 | + let largeArray = new Array(1e6).fill('leak'); // Large object |
| 186 | + return function() { |
| 187 | + // This closure keeps largeArray in memory |
| 188 | + console.log('Still holding largeArray of length:', largeArray.length); |
| 189 | + }; |
| 190 | +} |
| 191 | + |
| 192 | +let leaky = createLeak(); |
| 193 | +// Even if we don't need largeArray anymore, it's not garbage collected |
| 194 | +// because the closure (leaky) still references it. |
| 195 | +// To avoid the leak, set leaky = null when done: |
| 196 | +// leaky = null; |
| 197 | +``` |
| 198 | + |
| 199 | +--- |
| 200 | + |
| 201 | +## Summary |
| 202 | +Closures are a powerful feature in JavaScript, enabling data privacy, function factories, and more. Understanding closures is essential for interviews and real-world development. |
| 203 | + |
| 204 | +--- |
| 205 | + |
| 206 | +**Practice:** Try modifying the examples in `closures.js` and observe the results to deepen your understanding. |
0 commit comments