दिन 16: Scope, Hoisting और Closures
✅ आज आप सीखेंगे
- Global, function और block scope; scope chain
- Hoisting: var बनाम let/const बनाम function declarations, TDZ
- Closures: याद रखने वाले functions
- असली उपयोग: private state, factories, memoise, once
- मशहूर var-in-a-loop bug
🏢 असली दुनिया में
“Closures समझाइए” India में service companies से product startups तक सबसे ज़्यादा पूछे जाने वाले JavaScript interview सवालों में है। यह व्यावहारिक भी है: हर event handler, timer और React hook closures पर टिका है। आज आप इसे इतना समझेंगे कि sweet-shop उदाहरण से समझा सकें – और 20 साल से developers को उलझाने वाला bug ठीक कर सकें।
Scope और scope chain
const shop = 'Sharma Sweets'; // global scope
function makeInvoice(orderNo) { // function scope
const prefix = 'INV';
if (orderNo > 1000) {
const series = 'B'; // block scope
return `${shop} ${prefix}-${series}${orderNo}`; // looks outward: block → function → global
}
return `${shop} ${prefix}-${orderNo}`;
}
console.log(makeInvoice(42));
console.log(makeInvoice(1042));
console.log(typeof prefix, typeof series); // not visible outsideHoisting और temporal dead zone
console.log(greet('Riya')); // ✅ function declarations are hoisted with their body
function greet(n) { return 'Namaste ' + n; }
console.log(oldStyle); // undefined – var is hoisted but set to undefined
var oldStyle = 'var';
try {
console.log(modern); // ❌ let/const exist but are in the "temporal dead zone"
let modern = 'let';
} catch (err) {
console.log(err.name + ': ' + err.message);
}| Declaration | Hoist होता है? | Line से पहले |
|---|---|---|
| function name() {} | हाँ, body समेत | बुलाया जा सकता है |
| var x | हाँ, undefined के रूप में | undefined (चुपचाप bugs) |
| let / const | हाँ, पर uninitialised (TDZ) | ReferenceError |
| const f = () => {} | const जैसा | ReferenceError |
| class A {} | let जैसा (TDZ) | ReferenceError |
Closures: याद रखने वाले functions
function createCounter(label) {
let count = 0; // private – nobody outside can touch it
return function () { // this inner function "closes over" count and label
count++;
return `${label}: token #${count}`;
};
}
const counterA = createCounter('Counter A');
const counterB = createCounter('Counter B');
console.log(counterA());
console.log(counterA());
console.log(counterB()); // separate memory for each counter
console.log(counterA());
console.log(typeof count);Closure एक function है जो उस जगह के variables के साथ बँधा है जहाँ वह बना। createCounter ख़त्म होने के बाद भी लौटाया function अपने count तक पहुँचता है – जैसे दुकान की token machine अपना नंबर ख़ुद रखती है।
Closures के असली उपयोग
// 1. Private state with a public API (module pattern)
function createWallet(start) {
let balance = start;
return {
pay(amount) {
if (amount > balance) return `Insufficient balance (₹${balance})`;
balance -= amount;
return `Paid ₹${amount}, left ₹${balance}`;
},
get balance() { return balance; },
};
}
const wallet = createWallet(1000);
console.log(wallet.pay(650));
console.log(wallet.pay(500));
wallet.balance = 999999; // no setter → cannot be changed from outside
console.log(wallet.balance);
// 2. Function factories
const makeDiscount = percent => price => Math.round(price * (1 - percent / 100));
const diwali10 = makeDiscount(10), member5 = makeDiscount(5);
console.log(diwali10(900), member5(900));
// 3. once() – e.g. a coupon that can be applied only once
function once(fn) {
let done = false, result;
return (...args) => (done ? result : ((done = true), (result = fn(...args))));
}
const applyCoupon = once(code => `Coupon ${code} applied`);
console.log(applyCoupon('SWEET10'), '|', applyCoupon('AGAIN'));
// 4. Memoisation – cache slow results
function memo(fn) {
const cache = new Map();
return n => (cache.has(n) ? cache.get(n) + ' (cached)' : (cache.set(n, fn(n)), cache.get(n)));
}
const slowGst = memo(amount => amount * 0.05);
console.log(slowGst(2000), slowGst(2000));क्लासिक var-in-a-loop bug
const withVar = [], withLet = []; for (var i = 1; i <= 3; i++) withVar.push(() => i); // ONE shared i for all functions for (let j = 1; j <= 3; j++) withLet.push(() => j); // a NEW j for every round console.log(withVar.map(f => f())); // [4, 4, 4] ❌ console.log(withLet.map(f => f())); // [1, 2, 3] ✅
var function-scoped है, तो तीनों closures एक ही variable याद रखते हैं, जो loop ख़त्म होने पर 4 है। let हर iteration के लिए नई binding बनाता है। आधुनिक code के let/const इस्तेमाल का यही #1 कारण है।
असली task: स्वतंत्र token counters
<div id="counters"></div>
<script>
function createTokenCounter(name) {
let next = 1; // private per counter
const box = document.createElement('div');
box.style.cssText = 'display:inline-block;margin:6px;padding:12px 16px;border:1px solid #e5e7eb;border-radius:12px;text-align:center';
const title = document.createElement('b');
title.textContent = name;
const shown = document.createElement('div');
shown.style.fontSize = '2rem';
shown.textContent = '—';
const btn = document.createElement('button');
btn.textContent = 'Next token';
btn.addEventListener('click', () => { shown.textContent = 'A' + String(next++).padStart(3, '0'); }); // closure!
box.append(title, shown, btn);
return box;
}
document.querySelector('#counters').append(
createTokenCounter('Sweets counter'),
createTokenCounter('Namkeen counter'),
createTokenCounter('Billing'));
</script>🏅 आज के Job Skills
Practice tasks
🏋️ Practice – ख़ुद करें
Solution देखें
function createRateLimiter(limit) {
let calls = 0;
return function (action) {
calls++;
return calls <= limit ? `OK: ${action} (${calls}/${limit})` : 'Too many requests';
};
}
const sendOtp = createRateLimiter(3);
['otp', 'otp', 'otp', 'otp'].forEach(a => console.log(sendOtp(a)));Solution देखें
let x = 'global';
function outer() {
let x = 'outer';
function inner() { return x; }
return inner;
}
const f = outer();
x = 'changed global';
console.log(f()); // 'outer' – inner remembers outer's x, not the global oneQuick quiz
🧠 Quick Quiz – ख़ुद को जाँचें
Interview corner
💼 Interview Corner
Closure क्या है? व्यावहारिक उदाहरण दीजिए।
Closure function और उसके lexical environment का मेल है, जिससे बाहरी function के लौटने के बाद भी function बाहरी variables तक पहुँच रखता है। उदाहरण: private state वाला counter/rate-limiter, अपना item याद रखने वाले event handlers, या private cache वाला memoised function।
Hoisting समझाइए।
Compilation के दौरान declarations अपने scope में दर्ज होते हैं: function declarations पूरे, var undefined के रूप में, let/const/class uninitialised (temporal dead zone) – यानी मौजूद हैं पर declaration line से पहले पढ़े नहीं जा सकते।
सारांश
- Variables block → function → global क्रम में खोजे जाते हैं।
- Functions पूरे hoist; var undefined के रूप में; let/const TDZ में।
- Closures = functions + याद रखे variables → private state, factories, once, memo।
- साझा-var bug से बचने को loops में let।
Day 17: this, classes और object-oriented JavaScript – दोबारा उपयोगी Cart class बनाएँ।
