Advance JavaScript শিখুন বাংলায় (পর্ব ১) — Closure বিস্তারিত বোঝা

Closure - JavaScript ক্লোজার

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:

  1. একটা createBankAccount(initialBalance) ফাংশন বানাও যেটা deposit, withdraw, আর getBalance মেথড রিটার্ন করবে, ব্যালেন্স ভ্যারিয়েবল সম্পূর্ণ প্রাইভেট রেখে।
  2. লুপে var ব্যবহার করে ৫টা বাটন বানাও, তারপর let-এ পাল্টে দেখো ক্লিক ইভেন্টের আচরণ কীভাবে বদলায়।
  3. উপরের multiplier উদাহরণের মতো একটা greeting(name) ফাংশন ফ্যাক্টরি বানাও যেটা কাস্টম শুভেচ্ছা বার্তা তৈরি করবে।
  4. debounce ফাংশনটা তোমার কোনো প্রজেক্টের সার্চ ইনপুটে বসিয়ে টেস্ট করো — Network ট্যাবে কতবার কল হচ্ছে দেখো।

পরের পর্বে যা থাকছে

পরের পর্বে আমরা শিখব Scope ও Hoisting বিস্তারিতvar, let, const-এর মধ্যে পার্থক্য, আর JavaScript কীভাবে "পর্দার আড়ালে" ডিক্লেয়ারেশনগুলো উপরে তুলে নেয়, সেটা গভীরভাবে বোঝার চেষ্টা করব।

একটি মন্তব্য পোস্ট করুন

নবীনতর পূর্বতন

যোগাযোগ ফর্ম