ক্লোজার এবং স্কোপ (Closures & Scope)
স্কোপ — ভ্যারিয়েবলগুলো যেখানে থাকে (Scope — Where Variables Live)
জাভাস্ক্রিপ্টে প্রতিটি ভ্যারিয়েবলের একটি স্কোপ (scope) থাকে — অর্থাৎ আপনার কোডের যে নির্দিষ্ট অংশ থেকে ওই ভ্যারিয়েবলটিকে অ্যাক্সেস করা বা ব্যবহার করা যাবে। স্কোপকে অনেকটা একটি বাড়ির ভিন্ন ভিন্ন ঘরের মতো ভাবতে পারেন। আপনি রান্নাঘরে থাকলে সেখানকার সবকিছুই দেখতে পাবেন। চাইলে দরজা দিয়ে উঁকি মেরে বসার ঘরটিও (বাইরের স্কোপ বা outer scope) দেখতে পারেন। কিন্তু আপনি কখনোই বন্ধ করা শোবার ঘরের ভেতরে (অন্য ফাংশনের স্কোপ) কী চলছে তা দেখতে পাবেন না।
জাভাস্ক্রিপ্টে তিন ধরনের স্কোপ রয়েছে:
- গ্লোবাল স্কোপ (Global scope) — যেকোনো ফাংশন বা ব্লকের বাইরে ডিক্লেয়ার (declare) করা ভ্যারিয়েবল। এগুলো যেকোনো জায়গা থেকেই দেখা বা ব্যবহার করা যায়। অনেকটা বাড়ির হলওয়ের মতো — যেখানে দাঁড়িয়ে সবাই সবকিছু দেখতে পায়।
- ফাংশন স্কোপ (Function scope) — কোনো ফাংশনের ভেতরে
var,let, অথবাconstব্যবহার করে ডিক্লেয়ার করা ভ্যারিয়েবল। এগুলো কেবল ওই ফাংশনের ভেতর থেকেই দেখা যায়। - ব্লক স্কোপ (Block scope) — কার্লি ব্রেইস
{}বা দ্বিতীয় বন্ধনীর ভেতরেletঅথবাconstদিয়ে ডিক্লেয়ার করা ভ্যারিয়েবল। এগুলো কেবল ওই ব্লকের ভেতর থেকেই দেখা যায়।varএর কোনো ব্লক স্কোপ নেই — আর এটিইvarব্যবহার এড়িয়ে চলার অন্যতম প্রধান কারণ।
স্কোপের ব্যবহার বা Scope in Action
হয়েস্টিং — ভ্যারিয়েবলের সময় ভ্রমণ (Hoisting — Variables That Time-Travel)
আপনার লেখা কোডটি রান করার ঠিক আগে জাভাস্ক্রিপ্ট কিছুটা অদ্ভুত এক কাজ করে: এটি কোডের ডিক্লেয়ারেশনগুলোকে (declarations) উঠিয়ে বা হয়েস্ট (hoist) করে তাদের স্কোপের একেবারে ওপরে নিয়ে যায়। তবে মডজার ব্যাপার হলো — এখানে কেবল ডিক্লেয়ারেশনটিই ওপরে ওঠে, তার ভ্যালু বা মানটি নয়।
varডিক্লেয়ারেশনগুলো হয়েস্ট হয় এবং শুরুতে সেগুলোর মানundefinedসেট করা থাকে। আপনি এগুলোকে লেখার লাইনটির আগেও চাইলে ব্যবহার করতে পারেন (তবে সেক্ষেত্রে মান হিসেবেundefinedপাবেন)।letএবংconstডিক্লেয়ারেশনগুলোও হয়েস্ট হয়, কিন্তু সেগুলোকে একটি "টেম্পোরাল ডেড জোন (temporal dead zone বা TDZ)" এ রাখা হয় — ফলে ডিক্লেয়ার করার আগে এগুলোকে ব্যবহার করার চেষ্টা করলেই একটিReferenceErrorদেখাবে।- ফাংশন ডিক্লেয়ারেশনগুলো সম্পূর্ণভাবেই হয়েস্ট হয় — অর্থাৎ কোডের যেখানে লেখা থাকুক না কেন, আপনি চাইলে তার আগে থেকেই এদের কল করতে পারবেন!
- ফাংশন এক্সপ্রেশন এবং অ্যারো ফাংশনগুলো মূলত তাদের ভ্যারিয়েবলের (
var/let/const) নিয়ম মেনে চলে।
হয়েস্টিংয়ের কিছু চমক (Hoisting Surprises)
ক্লোজার — এক জাদুকরী সুপারপাওয়ার (Closures — The Superpower)
ক্লোজার (closure) হলো এমন একটি অবস্থা যখন কোনো ফাংশন, যেই স্কোপের ভেতরে সে তৈরি হয়েছিল, সেই স্কোপের কাজ সম্পূর্ণ শেষ হয়ে যাওয়ার পরও সেখানকার ভ্যারিয়েবলগুলোকে "মনে রাখতে" পারে। ব্যাপারটিকে অনেকটা কোনো পার্টি বা অনুষ্ঠানে তোলা ছবির মতো ভাবতে পারেন — অনুষ্ঠানটি শেষ হয়ে গেলেও, ছবিতে সেটির স্মৃতি ও উপস্থিত সবার কথা ঠিকই ধরা থাকে।
জাভাস্ক্রিপ্টে ক্লোজারগুলো প্রাকৃতিকভাবেই তৈরি হয়। যখনই একটি ফাংশনের ভেতরে আরেকটি ফাংশন লেখা হয়, তখন ওই ভেতরের ফাংশনটি তার বাইরের ফাংশনের ভ্যারিয়েবলগুলোতে চিরজীবনের জন্য অ্যাক্সেস পেয়ে যায়। এভাবেই জাভাস্ক্রিপ্টে ডেটা প্রাইভেসি (data privacy), কলব্যাক (callback), এবং অন্যান্য প্রচলিত প্যাটার্নগুলো কাজ করে।
ক্লোজারের ব্যবহার (Closures in Action)
লুপে ক্লোজারের ব্যবহার এবং IIFE (Closures in Loops & IIFE)
ক্লোজার নিয়ে অন্যতম বড় একটি ঝামেলার জায়গা হলো লুপ (loop)। var ব্যবহার করলে (যা ব্লক-স্কোপড নয়, বরং ফাংশন-স্কোপড), একটি লুপের সবগুলো ইটারেশনই (iteration) মূলত একই ভ্যারিয়েবলকে শেয়ার করে ব্যবহার করে। যার ফলে কলব্যাকটি রান করার সময় লুপটির কাজ শেষ হয়ে যায়, এবং ভ্যারিয়েবলটি শুধুমাত্র তার শেষ মানটিকে ধরে রাখে। let (যা ব্লক-স্কোপড) ব্যবহার করলে এই সমস্যার সমাধান হয়, কারণ তখন প্রতিটি ইটারেশনই নিজস্ব একটি কপি পেয়ে যায়।
একটি IIFE (Immediately Invoked Function Expression) হলো এমন একটি ফাংশন যা লেখার সাথে সাথেই রান করে। let এবং const আসার আগে, প্রাইভেট স্কোপ তৈরি করার জন্য মূলত IIFE ব্যবহার করা হতো। পুরোনো কোডে এবং কিছু বিল্ড প্যাটার্নে (build pattern) আপনি এখনও এটির ব্যবহার দেখতে পাবেন।