Advanced CSS সিরিজ শেষ হয়েছে গত পর্বে। এবার শুরু হচ্ছে Advanced JavaScript সিরিজ — আর প্রথম টপিক হলো Closure, JavaScript-এর সবচেয়ে গুরুত্বপূর্ণ কিন্তু প্রায়ই বিভ্রান্তিকর একটা কনসেপ্ট। এটা বুঝে গেলে React hooks, event handler, module pattern — অনেক কিছুই সহজ হয়ে যায়।
Closure কী?
Closure হলো একটা ফাংশন যেটা তার নিজের বাইরের (outer) স্কোপের ভ্যারিয়েবল "মনে রাখে" — এমনকি যদি সেই বাইরের ফাংশন আগেই রান শেষ করে ফেলে থাকে। সহজ কথায়: একটা ফাংশন যেখানে জন্ম নিয়েছে, সেই জায়গার ভ্যারিয়েবলগুলোর সাথে চিরকালের জন্য সংযুক্ত থাকে।
function outer() {
const message = "হ্যালো";
function inner() {
console.log(message); // outer-এর ভ্যারিয়েবল ব্যবহার করছে
}
return inner;
}
const myFunction = outer(); // outer() রান শেষ হয়ে গেছে
myFunction(); // তবুও "হ্যালো" প্রিন্ট হবে!
খেয়াল করো — outer() কল করে সাথে সাথে সেটা রিটার্ন করে দিচ্ছে, তার কাজ শেষ। কিন্তু inner ফাংশনটা যখন পরে কল হচ্ছে, তখনও সে message-এর মান মনে রাখছে। এটাই Closure — ফাংশন তার "জন্মস্থান"-এর ভ্যারিয়েবলগুলো ধরে রাখে।
সবচেয়ে সাধারণ ব্যবহার — প্রাইভেট ভ্যারিয়েবল
JavaScript-এ সরাসরি "private" ভ্যারিয়েবল বলে কিছু নেই (ক্লাসের #field ছাড়া), কিন্তু Closure দিয়ে সেই আচরণ তৈরি করা যায়:
function createCounter() {
let count = 0; // এই ভ্যারিয়েবল বাইরে থেকে সরাসরি অ্যাক্সেস করা যাবে না
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getValue() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.count); // undefined — সরাসরি অ্যাক্সেস করা যায় না!
এখানে count ভ্যারিয়েবলটা সম্পূর্ণ সুরক্ষিত — শুধু increment, decrement, আর getValue ফাংশনগুলোই এটা পাল্টাতে পারে। এটাকে বলে Module Pattern — Closure-এর সবচেয়ে ব্যবহারিক প্রয়োগগুলোর একটা।
লাইভ ডেমো — Closure দিয়ে বানানো কাউন্টার
নিচের বাটনগুলো একটা Closure-ভিত্তিক কাউন্টার ব্যবহার করছে — প্রতিটা ক্লিকে count ভ্যারিয়েবলটা "মনে থেকে যাচ্ছে", যদিও এটা কোনো গ্লোবাল ভ্যারিয়েবল না।
0
এখানে count ভ্যারিয়েবলটা createCounter ফাংশনের ভেতরে "আটকে" আছে — বাটনে ক্লিক করলে increment/decrement ফাংশন সেই আটকে থাকা ভ্যারিয়েবল অ্যাক্সেস করছে, প্রতিবার নতুন করে শুরু না করে।
সাধারণ ভুল — লুপে Closure
Closure নিয়ে সবচেয়ে ক্লাসিক ভুল হয় লুপের ভেতরে setTimeout ব্যবহার করার সময়:
// ❌ var দিয়ে — সবাই "3" প্রিন্ট করবে
for (var i = 1; i <= 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// আউটপুট: 3, 3, 3 (প্রত্যাশিত ছিল 1, 2, 3)
এর কারণ — var ফাংশন-স্কোপড, তাই পুরো লুপে একটাই i শেয়ার হয়। লুপ শেষ হওয়ার পর i-এর মান হয়ে যায় ৪ (লুপ কন্ডিশন ব্যর্থ হওয়ার আগে), আর সব setTimeout সেই একই i-কে রেফার করে... আসলে এখানে ৩ প্রিন্ট হয় কারণ লুপ শর্ত `i<=3` ব্যর্থ হওয়ার সময়ের মান।
// ✅ let দিয়ে — প্রতিটা ইটারেশনে নতুন block-scoped ভ্যারিয়েবল
for (let i = 1; i <= 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// আউটপুট: 1, 2, 3 (যেমনটা প্রত্যাশিত)
let ব্লক-স্কোপড, তাই প্রতিটা লুপ ইটারেশনে একটা নতুন i তৈরি হয় — প্রতিটা setTimeout-এর Closure তার নিজস্ব আলাদা i-কে ধরে রাখে।
লাইভ ডেমো — var বনাম let
নিচের বাটনে চাপো — দুটো ভিন্ন আচরণ পাশাপাশি দেখো।
var: —
let: —
Closure দিয়ে ফাংশন ফ্যাক্টরি
Closure দিয়ে "কাস্টমাইজড" ফাংশন বানানো যায় — এক ফাংশন থেকে বিভিন্ন আচরণের অনেক ফাংশন তৈরি হয়:
function multiplier(factor) {
return function (number) {
return number * factor;
};
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
এখানে double আর triple দুটোই একই ফাংশন থেকে বানানো, কিন্তু প্রতিটা তার নিজের factor মনে রাখছে — কারণ প্রতিবার multiplier() কল করার সময় একটা নতুন Closure তৈরি হয়।
একটা সম্পূর্ণ উদাহরণ — debounce ফাংশন
বাস্তব জগতে Closure-এর একটা খুব দরকারি প্রয়োগ হলো debounce — সার্চ বক্সে টাইপ করার সময় প্রতিটা কী-প্রেসে API কল না করে, টাইপিং থামার পর একবার কল করা।
function debounce(fn, delay) {
let timeoutId; // এই ভ্যারিয়েবল Closure-এ আটকে থাকবে
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn(...args);
}, delay);
};
}
// ব্যবহার
const searchInput = document.getElementById("search");
const handleSearch = debounce((value) => {
console.log("সার্চ করা হচ্ছে:", value);
// এখানে API কল হবে
}, 500);
searchInput.addEventListener("input", (e) => {
handleSearch(e.target.value);
});
এখানে timeoutId ভ্যারিয়েবলটা debounce()-এর রিটার্ন করা ফাংশনের Closure-এ আটকে আছে — প্রতিবার টাইপ করলে আগের টাইমার বাতিল করে নতুন টাইমার শুরু হয়, শুধু টাইপিং থামলেই আসল ফাংশনটা রান হয়।
নিজে চেষ্টা করো
Exercise:
- একটা
createBankAccount(initialBalance)ফাংশন বানাও যেটাdeposit,withdraw, আরgetBalanceমেথড রিটার্ন করবে, ব্যালেন্স ভ্যারিয়েবল সম্পূর্ণ প্রাইভেট রেখে। - লুপে
varব্যবহার করে ৫টা বাটন বানাও, তারপরlet-এ পাল্টে দেখো ক্লিক ইভেন্টের আচরণ কীভাবে বদলায়। - উপরের
multiplierউদাহরণের মতো একটাgreeting(name)ফাংশন ফ্যাক্টরি বানাও যেটা কাস্টম শুভেচ্ছা বার্তা তৈরি করবে। debounceফাংশনটা তোমার কোনো প্রজেক্টের সার্চ ইনপুটে বসিয়ে টেস্ট করো — Network ট্যাবে কতবার কল হচ্ছে দেখো।
পরের পর্বে যা থাকছে
পরের পর্বে আমরা শিখব Scope ও Hoisting বিস্তারিত — var, let, const-এর মধ্যে পার্থক্য, আর JavaScript কীভাবে "পর্দার আড়ালে" ডিক্লেয়ারেশনগুলো উপরে তুলে নেয়, সেটা গভীরভাবে বোঝার চেষ্টা করব।