আগের পর্বে আমরা 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:
- একটা ফাংশনে
varআরletদুটোই ব্যবহার করে একটাifব্লকের ভেতরে ডিক্লেয়ার করো, তারপর ব্লকের বাইরে থেকে দুটোই অ্যাক্সেস করার চেষ্টা করো — পার্থক্য লক্ষ্য করো। - একটা ফাংশন এক্সপ্রেশন ডিক্লেয়ার করার আগে কল করে দেখো কী এরর আসে।
- উপরের বাগযুক্ত বাটন উদাহরণটা নিজে বানাও, তারপর
var-কেlet-এ পাল্টে সমস্যা সমাধান করো। constদিয়ে একটা ভ্যারিয়েবল ডিক্লেয়ার করে পরে সেটার মান পাল্টানোর চেষ্টা করো — কী এরর আসে সেটা বুঝে লিখে রাখো।
পরের পর্বে যা থাকছে
পরের পর্বে আমরা শিখব `this` কীওয়ার্ড — JavaScript-এর আরেকটা বিভ্রান্তিকর কিন্তু গুরুত্বপূর্ণ কনসেপ্ট, যেটা কনটেক্সট অনুযায়ী ভিন্ন ভিন্ন জিনিস বোঝায়।