Advance JavaScript শিখুন বাংলায় (পর্ব ২) — Scope ও Hoisting বিস্তারিত বোঝা

Scope ও Hoisting বিস্তারিত

আগের পর্বে আমরা Closure শিখেছিলাম, যেটা কাজ করে Scope-এর ধারণার উপর ভিত্তি করে। এই পর্বে আমরা সেই ভিত্তিটাই আরও গভীরভাবে দেখব — Scope (ভ্যারিয়েবল কোথায় "দেখা যায়") আর Hoisting (JavaScript কীভাবে পর্দার আড়ালে ডিক্লেয়ারেশন উপরে তুলে নেয়)।

Scope কী?

Scope নির্ধারণ করে কোন ভ্যারিয়েবল কোথা থেকে অ্যাক্সেস করা যাবে। JavaScript-এ প্রধানত তিন ধরনের স্কোপ আছে:

স্কোপ কোথায় প্রযোজ্য
Global Scope পুরো ফাইলের যেকোনো জায়গা থেকে অ্যাক্সেসযোগ্য
Function Scope শুধু সেই ফাংশনের ভেতরে (var এভাবে কাজ করে)
Block Scope শুধু { }-এর ভেতরে (let/const এভাবে কাজ করে)
function demo() {
  var functionScoped = "আমি var";

  if (true) {
    let blockScoped = "আমি let";
    var alsoFunctionScoped = "আমিও var (block-এ থাকলেও)";
    console.log(functionScoped); // ✅ কাজ করবে
    console.log(blockScoped); // ✅ কাজ করবে
  }

  console.log(alsoFunctionScoped); // ✅ কাজ করবে — var block ভেঙে বের হয়ে আসে
  console.log(blockScoped); // ❌ Error! let ব্লকের বাইরে দেখা যায় না
}

এখানে সবচেয়ে গুরুত্বপূর্ণ শিক্ষা — var শুধু ফাংশনের সীমানা মানে, if/for-এর মতো ব্লকের সীমানা মানে না। এই কারণেই আধুনিক JavaScript-এ var-এর বদলে let/const ব্যবহার করার পরামর্শ দেওয়া হয়।

Scope Chain — বাইরে খোঁজা

কোনো ভ্যারিয়েবল ব্যবহার করলে JavaScript প্রথমে বর্তমান স্কোপে খোঁজে, না পেলে একটু "বাইরে" খোঁজে, তারপর আরও বাইরে — এভাবে Global স্কোপ পর্যন্ত। একে বলে Scope Chain

const level1 = "গ্লোবাল";

function outer() {
  const level2 = "আউটার";

  function inner() {
    const level3 = "ইনার";
    console.log(level1); // পাওয়া যাবে গ্লোবাল স্কোপে খুঁজে
    console.log(level2); // পাওয়া যাবে outer-এর স্কোপে খুঁজে
    console.log(level3); // পাওয়া যাবে নিজের স্কোপেই
  }

  inner();
}

outer();

এটাই মূলত Closure-এর ভিত্তি (আগের পর্বে যা শিখেছি) — inner() তার বাইরের স্কোপের ভ্যারিয়েবল অ্যাক্সেস করতে পারছে Scope Chain-এর মাধ্যমে।

Hoisting কী?

Hoisting হলো JavaScript-এর একটা আচরণ, যেখানে ফাংশন আর ভ্যারিয়েবলের ডিক্লেয়ারেশন (declaration) কোড রান হওয়ার আগেই স্কোপের উপরে "তুলে নেওয়া" হয় — যদিও ভিজ্যুয়ালি কোডটা নিচেই লেখা থাকে।

console.log(myVar); // undefined (error না!)
var myVar = "হ্যালো";

// উপরের কোডটা আসলে JavaScript এভাবে বোঝে:
var myVar; // ডিক্লেয়ারেশন উপরে উঠে গেছে
console.log(myVar); // undefined
myVar = "হ্যালো"; // অ্যাসাইনমেন্ট নিজের জায়গায় থেকে গেছে

গুরুত্বপূর্ণ: শুধু ডিক্লেয়ারেশন (var myVar) উপরে ওঠে, মান (= "হ্যালো") ওঠে না। তাই ব্যবহারের আগে অ্যাক্সেস করলে এরর হয় না, কিন্তু undefined পাওয়া যায়।

ফাংশন Hoisting

সাধারণ ফাংশন ডিক্লেয়ারেশন সম্পূর্ণভাবে (নাম + বডি) হোয়িস্ট হয় — তাই ডিক্লেয়ার করার আগেই কল করা যায়!

sayHello(); // ✅ কাজ করবে — "হ্যালো!" প্রিন্ট হবে

function sayHello() {
  console.log("হ্যালো!");
}

কিন্তু ফাংশন এক্সপ্রেশন (একটা ভ্যারিয়েবলে অ্যাসাইন করা ফাংশন) এভাবে কাজ করে না — কারণ শুধু ভ্যারিয়েবলের ডিক্লেয়ারেশন হোয়িস্ট হয়, ফাংশনটা না:

sayBye(); // ❌ TypeError: sayBye is not a function

var sayBye = function () {
  console.log("বিদায়!");
};

let/const আর Temporal Dead Zone

let আর const-ও আসলে হোয়িস্ট হয়, কিন্তু var-এর মতো undefined দিয়ে শুরু হয় না — এদের একটা "মৃত অঞ্চল" (Temporal Dead Zone বা TDZ) থাকে, যেখানে অ্যাক্সেস করলে এরর হয়।

console.log(myLetVar); // ❌ ReferenceError: Cannot access before initialization
let myLetVar = "হ্যালো";
ধরন ডিক্লেয়ার করার আগে অ্যাক্সেস করলে
var undefined (এরর হয় না)
let / const ReferenceError
function declaration স্বাভাবিকভাবে কাজ করে

এই কারণেই let/const বেশি নিরাপদ — এগুলো ভুল করে ডিক্লেয়ার করার আগে ব্যবহার করলে সাথে সাথে এরর দেখায়, যেখানে var চুপচাপ undefined দিয়ে বাগ লুকিয়ে রাখে।

লাইভ ডেমো — নিজে চালিয়ে দেখো

নিচের বাটনে চাপো — একটা ছোট কোড রান হবে যেখানে var-এর hoisting আচরণ দেখা যাবে (কনসোলের বদলে সরাসরি রেজাল্ট দেখানো হচ্ছে)।

function demo() {
  console.log(x); // এখানে কী হবে?
  var x = 5;
}
demo();

লক্ষ্য করো — x ব্যবহার করা হচ্ছে ডিক্লেয়ার করার আগে, তবুও এরর হচ্ছে না — কারণ var x হোয়িস্ট হয়ে গেছে।

const আর Hoisting

const-ও let-এর মতোই আচরণ করে TDZ-এর ক্ষেত্রে, তবে একটা অতিরিক্ত নিয়ম আছে — ডিক্লেয়ার করার সাথে সাথেই মান দিতে হয়:

const price; // ❌ SyntaxError: Missing initializer
price = 100;

const price = 100; // ✅ ঠিক আছে — সাথে সাথে মান দেওয়া হয়েছে

একটা সম্পূর্ণ উদাহরণ — সাধারণ ভুল ধরা

চলো একটা বাস্তব বাগ দেখি, যেটা Scope আর Hoisting না বুঝলে খুঁজে পাওয়া কঠিন হয়ে যায়:

// ❌ বাগ: সব বাটনে ক্লিক করলে একই মেসেজ দেখাবে
function setupButtonsBuggy() {
  for (var i = 0; i < 3; i++) {
    const btn = document.createElement("button");
    btn.textContent = `বাটন ${i}`;
    btn.addEventListener("click", function () {
      alert("তুমি বাটন " + i + " চাপলে"); // সবসময় "3" দেখাবে!
    });
    document.body.appendChild(btn);
  }
}

// ✅ সমাধান: var-এর বদলে let ব্যবহার করা
function setupButtonsFixed() {
  for (let i = 0; i < 3; i++) {
    const btn = document.createElement("button");
    btn.textContent = `বাটন ${i}`;
    btn.addEventListener("click", function () {
      alert("তুমি বাটন " + i + " চাপলে"); // ঠিকমতো 0, 1, 2 দেখাবে
    });
    document.body.appendChild(btn);
  }
}

এই বাগটা ঠিক আগের পর্বের setTimeout-এর সমস্যার মতোই — var ফাংশন-স্কোপড হওয়ায় সব ইভেন্ট লিসেনার একই i শেয়ার করে, let ব্যবহার করলে প্রতিটা ইটারেশনে আলাদা i তৈরি হয়।

নিজে চেষ্টা করো

Exercise:

  1. একটা ফাংশনে var আর let দুটোই ব্যবহার করে একটা if ব্লকের ভেতরে ডিক্লেয়ার করো, তারপর ব্লকের বাইরে থেকে দুটোই অ্যাক্সেস করার চেষ্টা করো — পার্থক্য লক্ষ্য করো।
  2. একটা ফাংশন এক্সপ্রেশন ডিক্লেয়ার করার আগে কল করে দেখো কী এরর আসে।
  3. উপরের বাগযুক্ত বাটন উদাহরণটা নিজে বানাও, তারপর var-কে let-এ পাল্টে সমস্যা সমাধান করো।
  4. const দিয়ে একটা ভ্যারিয়েবল ডিক্লেয়ার করে পরে সেটার মান পাল্টানোর চেষ্টা করো — কী এরর আসে সেটা বুঝে লিখে রাখো।

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

পরের পর্বে আমরা শিখব `this` কীওয়ার্ড — JavaScript-এর আরেকটা বিভ্রান্তিকর কিন্তু গুরুত্বপূর্ণ কনসেপ্ট, যেটা কনটেক্সট অনুযায়ী ভিন্ন ভিন্ন জিনিস বোঝায়।

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

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

যোগাযোগ ফর্ম