Lesson ১৫৮ মিনিট পড়া

ক্লোজার এবং স্কোপ (Closures & Scope)

ভ্যারিয়েবলগুলো কোথায় থাকে, কে তাদের দেখতে পায় এবং ক্লোজারের পেছনের জাদুগুলো সম্পর্কে বুঝুন

স্কোপ — ভ্যারিয়েবলগুলো যেখানে থাকে (Scope — Where Variables Live)

জাভাস্ক্রিপ্টে প্রতিটি ভ্যারিয়েবলের একটি স্কোপ (scope) থাকে — অর্থাৎ আপনার কোডের যে নির্দিষ্ট অংশ থেকে ওই ভ্যারিয়েবলটিকে অ্যাক্সেস করা বা ব্যবহার করা যাবে। স্কোপকে অনেকটা একটি বাড়ির ভিন্ন ভিন্ন ঘরের মতো ভাবতে পারেন। আপনি রান্নাঘরে থাকলে সেখানকার সবকিছুই দেখতে পাবেন। চাইলে দরজা দিয়ে উঁকি মেরে বসার ঘরটিও (বাইরের স্কোপ বা outer scope) দেখতে পারেন। কিন্তু আপনি কখনোই বন্ধ করা শোবার ঘরের ভেতরে (অন্য ফাংশনের স্কোপ) কী চলছে তা দেখতে পাবেন না।

জাভাস্ক্রিপ্টে তিন ধরনের স্কোপ রয়েছে:

  • গ্লোবাল স্কোপ (Global scope) — যেকোনো ফাংশন বা ব্লকের বাইরে ডিক্লেয়ার (declare) করা ভ্যারিয়েবল। এগুলো যেকোনো জায়গা থেকেই দেখা বা ব্যবহার করা যায়। অনেকটা বাড়ির হলওয়ের মতো — যেখানে দাঁড়িয়ে সবাই সবকিছু দেখতে পায়।
  • ফাংশন স্কোপ (Function scope) — কোনো ফাংশনের ভেতরে var, let, অথবা const ব্যবহার করে ডিক্লেয়ার করা ভ্যারিয়েবল। এগুলো কেবল ওই ফাংশনের ভেতর থেকেই দেখা যায়।
  • ব্লক স্কোপ (Block scope) — কার্লি ব্রেইস {} বা দ্বিতীয় বন্ধনীর ভেতরে let অথবা const দিয়ে ডিক্লেয়ার করা ভ্যারিয়েবল। এগুলো কেবল ওই ব্লকের ভেতর থেকেই দেখা যায়। var এর কোনো ব্লক স্কোপ নেই — আর এটিই var ব্যবহার এড়িয়ে চলার অন্যতম প্রধান কারণ।
Click chart to zoom
জাভাস্ক্রিপ্টে স্কোপ চেইন (Scope chain) লুকআপ বা সন্ধান: যখন আপনি কোনো ভ্যারিয়েবলকে ডাকেন, তখন জাভাস্ক্রিপ্ট ইঞ্জিনটি একদম ভেতরের স্কোপ থেকে শুরু করে বাইরের দিকে খুঁজতে থাকে, যতক্ষণ পর্যন্ত না সেটিকে খুঁজে পাওয়া যায় অথবা একটি ReferenceError ছুঁড়ে দেয়।

স্কোপের ব্যবহার বা Scope in Action

// Global scope
let globalVar = "I'm global!";
function showScope() {
// Function scope
let functionVar = "I'm in a function!";
console.log(globalVar); // ✅ can see global
console.log(functionVar); // ✅ can see own variable
if (true) {
// Block scope
let blockVar = "I'm in a block!";
const alsoBlock = "Me too!";
var notBlock = "I ignore blocks! (var is function-scoped)";
console.log(blockVar); // ✅
console.log(functionVar); // ✅ can see outer function scope
}
// console.log(blockVar); // ❌ ReferenceError — block scope!
console.log(notBlock); // ✅ var escapes the block!
}
showScope();
// console.log(functionVar); // ❌ ReferenceError — function scope!
Output
I'm global!
I'm in a function!
I'm in a block!
I'm in a function!
I ignore blocks! (var is function-scoped)

হয়েস্টিং — ভ্যারিয়েবলের সময় ভ্রমণ (Hoisting — Variables That Time-Travel)

আপনার লেখা কোডটি রান করার ঠিক আগে জাভাস্ক্রিপ্ট কিছুটা অদ্ভুত এক কাজ করে: এটি কোডের ডিক্লেয়ারেশনগুলোকে (declarations) উঠিয়ে বা হয়েস্ট (hoist) করে তাদের স্কোপের একেবারে ওপরে নিয়ে যায়। তবে মডজার ব্যাপার হলো — এখানে কেবল ডিক্লেয়ারেশনটিই ওপরে ওঠে, তার ভ্যালু বা মানটি নয়।

  • var ডিক্লেয়ারেশনগুলো হয়েস্ট হয় এবং শুরুতে সেগুলোর মান undefined সেট করা থাকে। আপনি এগুলোকে লেখার লাইনটির আগেও চাইলে ব্যবহার করতে পারেন (তবে সেক্ষেত্রে মান হিসেবে undefined পাবেন)।
  • let এবং const ডিক্লেয়ারেশনগুলোও হয়েস্ট হয়, কিন্তু সেগুলোকে একটি "টেম্পোরাল ডেড জোন (temporal dead zone বা TDZ)" এ রাখা হয় — ফলে ডিক্লেয়ার করার আগে এগুলোকে ব্যবহার করার চেষ্টা করলেই একটি ReferenceError দেখাবে।
  • ফাংশন ডিক্লেয়ারেশনগুলো সম্পূর্ণভাবেই হয়েস্ট হয় — অর্থাৎ কোডের যেখানে লেখা থাকুক না কেন, আপনি চাইলে তার আগে থেকেই এদের কল করতে পারবেন!
  • ফাংশন এক্সপ্রেশন এবং অ্যারো ফাংশনগুলো মূলত তাদের ভ্যারিয়েবলের (var/let/const) নিয়ম মেনে চলে।

হয়েস্টিংয়ের কিছু চমক (Hoisting Surprises)

// Function declarations are fully hoisted
console.log(greet("World")); // "Hello, World!" — works!
function greet(name) {
return `Hello, ${name}!`;
}
// var is hoisted but initialized to undefined
console.log(x); // undefined (not an error!)
var x = 10;
console.log(x); // 10
// let/const are hoisted but in the "temporal dead zone"
// console.log(y); // ❌ ReferenceError: Cannot access 'y' before initialization
let y = 20;
console.log(y); // 20
// Arrow functions follow their variable rules
// console.log(double(5)); // ❌ ReferenceError (const is in TDZ)
const double = (n) => n * 2;
console.log(double(5)); // 10
Output
Hello, World!
undefined
10
20
10

ক্লোজার — এক জাদুকরী সুপারপাওয়ার (Closures — The Superpower)

ক্লোজার (closure) হলো এমন একটি অবস্থা যখন কোনো ফাংশন, যেই স্কোপের ভেতরে সে তৈরি হয়েছিল, সেই স্কোপের কাজ সম্পূর্ণ শেষ হয়ে যাওয়ার পরও সেখানকার ভ্যারিয়েবলগুলোকে "মনে রাখতে" পারে। ব্যাপারটিকে অনেকটা কোনো পার্টি বা অনুষ্ঠানে তোলা ছবির মতো ভাবতে পারেন — অনুষ্ঠানটি শেষ হয়ে গেলেও, ছবিতে সেটির স্মৃতি ও উপস্থিত সবার কথা ঠিকই ধরা থাকে।

জাভাস্ক্রিপ্টে ক্লোজারগুলো প্রাকৃতিকভাবেই তৈরি হয়। যখনই একটি ফাংশনের ভেতরে আরেকটি ফাংশন লেখা হয়, তখন ওই ভেতরের ফাংশনটি তার বাইরের ফাংশনের ভ্যারিয়েবলগুলোতে চিরজীবনের জন্য অ্যাক্সেস পেয়ে যায়। এভাবেই জাভাস্ক্রিপ্টে ডেটা প্রাইভেসি (data privacy), কলব্যাক (callback), এবং অন্যান্য প্রচলিত প্যাটার্নগুলো কাজ করে।

ক্লোজারের ব্যবহার (Closures in Action)

// Basic closure — inner function remembers outer variables
function createCounter() {
let count = 0; // this variable is "enclosed"
return {
increment() { count++; return count; },
decrement() { count--; return count; },
getCount() { return count; }
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.increment()); // 3
console.log(counter.decrement()); // 2
// count is private! We can't access it directly:
// console.log(count); // ❌ ReferenceError
// Each call creates a NEW closure with its own variables
const counter2 = createCounter();
console.log(counter2.getCount()); // 0 (independent!)
console.log(counter.getCount()); // 2 (still has its own count)
// Practical use: function factory
function createGreeter(greeting) {
return (name) => `${greeting}, ${name}!`;
}
const sayHello = createGreeter("Hello");
const sayHola = createGreeter("Hola");
console.log(sayHello("Kaisar")); // "Hello, Kaisar!"
console.log(sayHola("Kaisar")); // "Hola, Kaisar!"
Output
1
2
3
2
0
2
Hello, Kaisar!
Hola, Kaisar!

লুপে ক্লোজারের ব্যবহার এবং IIFE (Closures in Loops & IIFE)

ক্লোজার নিয়ে অন্যতম বড় একটি ঝামেলার জায়গা হলো লুপ (loop)। var ব্যবহার করলে (যা ব্লক-স্কোপড নয়, বরং ফাংশন-স্কোপড), একটি লুপের সবগুলো ইটারেশনই (iteration) মূলত একই ভ্যারিয়েবলকে শেয়ার করে ব্যবহার করে। যার ফলে কলব্যাকটি রান করার সময় লুপটির কাজ শেষ হয়ে যায়, এবং ভ্যারিয়েবলটি শুধুমাত্র তার শেষ মানটিকে ধরে রাখে। let (যা ব্লক-স্কোপড) ব্যবহার করলে এই সমস্যার সমাধান হয়, কারণ তখন প্রতিটি ইটারেশনই নিজস্ব একটি কপি পেয়ে যায়।

একটি IIFE (Immediately Invoked Function Expression) হলো এমন একটি ফাংশন যা লেখার সাথে সাথেই রান করে। let এবং const আসার আগে, প্রাইভেট স্কোপ তৈরি করার জন্য মূলত IIFE ব্যবহার করা হতো। পুরোনো কোডে এবং কিছু বিল্ড প্যাটার্নে (build pattern) আপনি এখনও এটির ব্যবহার দেখতে পাবেন।

লুপের সমস্যা এবং IIFE বা The Loop Gotcha & IIFE

// The classic closure-in-loop bug with var
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log("var:", i), 100);
}
// Prints: 3, 3, 3 — because var is shared, and i is 3 when callbacks run
// Fixed with let — each iteration gets its own i
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log("let:", j), 200);
}
// Prints: 0, 1, 2 — each callback closes over its own j
// IIFE — Immediately Invoked Function Expression
// Creates a private scope
const gameModule = (() => {
let score = 0; // private!
let highScore = 0; // private!
return {
addPoints(pts) {
score += pts;
if (score > highScore) highScore = score;
return score;
},
reset() {
score = 0;
},
getHighScore() {
return highScore;
}
};
})(); // <-- () immediately invokes it
console.log(gameModule.addPoints(100)); // 100
console.log(gameModule.addPoints(50)); // 150
gameModule.reset();
console.log(gameModule.getHighScore()); // 150
// console.log(score); // ❌ ReferenceError — score is private!
Output
var: 3
var: 3
var: 3
let: 0
let: 1
let: 2
100
150
150
Note: জাভাস্ক্রিপ্ট মূলত ক্লোজারের সাহায্যেই প্রপার বা আসল প্রাইভেট ভ্যারিয়েবল (private variables) তৈরি করে। 'private' কিওয়ার্ড সরাসরি ব্যবহার করা যায় এমন অন্যান্য ভাষাগুলোর তুলনায়, জাভাস্ক্রিপ্টে (class #private ফিল্ড আসার আগে পর্যন্ত) ডেটা লুকিয়ে রাখার জন্য ক্লোজার ব্যবহার করা হতো। এর প্যাটার্নটি হলো: একটি ফাংশনের ভেতর ভ্যারিয়েবল তৈরি করুন, এরপর এমন ফাংশন রিটার্ন করুন যারা সেই ভ্যারিয়েবলগুলোকে অ্যাক্সেস করতে পারে। বাইরের কোডগুলো এই রিটার্ন করা ফাংশনগুলো ব্যবহার করতে পারলেও ভ্যারিয়েবলগুলোতে সরাসরি কখনো হাত দিতে পারবে না। এটি অনেকটা এটিএম (ATM) মেশিনের মতো — আপনি শুধু বাটন চেপেই বা পিন দিয়েই টাকা নিতে পারবেন, কিন্তু সরাসরি হাত ঢুকিয়ে ক্যাশ বাক্স থেকে টাকা বের করার কোনো উপায় নেই।
চ্যালেঞ্জ

ছোট কুইজ

'let' এর এমন কোন স্কোপ আছে যা 'var' এর নেই?
Async Programming