Skip to content

Chapter 15: JavaScript Core Concepts

15-1: Dynamic ডায়নামাইট

কিছু পোলাপান কারণে-অকারণে ঘাউরামি করে। তারা যেটা বলে, সেটাই করে। কোনো ফ্লেক্সিবিলিটি নাই। চেইঞ্জ করা যায় না। কিছু বলাও যায়। এইটার কিছু সুবিধা আর কিছু অসুবিধা তো আছেই।

আবার কিছু পোলাপান আছে মাটির মানুষ। তাদের যা বলবি, সেটার সাথেই আছে এবং খুবই ফ্লেক্সিবিল। এত ফ্লেক্সিবিল হওয়ারও কিছু সুবিধা-অসুবিধা তো আছেই।

জাভাস্ক্রিপ্ট একটু ফ্লেক্সিবল টাইপের। বিশেষ করে ভেরিয়েবল ডিক্লেয়ার করার ক্ষেত্রে জাভাস্ক্রিপ্ট অনেক ফ্লেক্সিবল। সেজন্য জাভাস্ক্রিপ্টে একটা ভেরিয়েবল লিখলে সেটার মধ্যে সংখ্যা রাখতে পারবি। চাইলে স্ট্রিং, বুলিয়ান রাখতে পারবি। আবার চাইলে স্ট্রিংওয়ালা ভেরিয়েবলের মান চেইঞ্জ করে বুলিয়ানও রেখে দিতে পারবি। সে মাইন্ড করবে না। এমনকি চাইলে সেখানে অ্যারে বা অবজেক্ট, এমনকি ফাংশনও রাখতে পারবি।

javascript
  let data = 42; // Number
  data = "Hello, World!"; // String
  data = true; // Boolean
  data = [10, 50, 66]; // array
  data = { name: 'cat', say: 'meow' } // object
  console.log(data)

Output: {name: 'cat', say: 'meow'}

যদিও আমাদের এইভাবে করা উচিত না। তবে করা যে যায়, সেটা জানা থাকলে ভালো। কখনো এইরকম কেউ করলে সেটাকে না করতে বলতে পারবি। কারণ, ভেরিয়েবল ডিক্লেয়ার করতে পয়সা লাগে না। সেজন্য বিভিন্ন জিনিস এবং বিভিন্ন ধরনের জিনিস আলাদা আলাদা ভেরিয়েবলে রাখাই ভালো এবং সেগুলার জন্য মিনিংফুল নাম অর্থাৎ নাম দেখলেই বুঝা যায়, এইটার মধ্যে কী আছে। সেই অনুসারে নাম রাখাই ভালো।

জাভাস্ক্রিপ্ট কীভাবে বুঝে, কোনটার মধ্যে কী ধরনের জিনিস আছে?

সেটা হচ্ছে, জাভাস্ক্রিপ্ট ভেরিয়েবলের মান দেখে ঠিক করে নেয়। সেটার মধ্যে কী আছে। অন্য অনেক প্রোগ্রামিং ল্যাঙ্গুয়েজ আছে, যেগুলাতে ভেরিয়েবল ডিক্লেয়ার করার সময়ই বলে দিতে হয়, কী ধরনের মান রাখবি। এক্ষেত্রে জাভাস্ক্রিপ্ট পুরাই ফ্লেক্সিবল। এইখানে বলে দেয়া লাগে না, কী ধরনের মান রাখবি।

এই জন্য জাভাস্ক্রিপ্টকে বলে dynamic typing প্রোগ্রামিং ল্যাঙ্গুয়েজ। অর্থাৎ জাভাস্ক্রিপ্টের ভেরিয়েবলের টাইপ ডাইনামিক। এইটা মান থেকে ডাটার টাইপ ধরে নেয়। আবার কেউ কেউ এইটাকে loosely typed প্রোগ্রামিং ল্যাঙ্গুয়েজও বলে।

এইটার সুবিধা হচ্ছে কোড লেখা সহজ। আলাদা ডেটা টাইপ ডিক্লেয়ার করার দরকার পড়ে না। দ্রুত ডেভেলপমেন্ট করা যায়। আর অসুবিধা হচ্ছে, ভুল টাইপ নিয়ে কাজ করলে সমস্যা তৈরি হতে পারে। টাইপের ভুল বোঝাবুঝি ডিবাগ করা কঠিন হয়ে যেতে পারে।

15-2: প্রাইমারি স্কুলের Primitive

Primitive Types

Number, String, Boolean এদেরকে বলা হয় primitive type, মানে এগুলো হচ্ছে basic ডাটা টাইপ। নাম্বারের মধ্যে তুই শুধু একটা নাম্বার রাখতে পারবি, স্ট্রিংয়ের মধ্যে একটা string রাখতে পারবি, আর Boolean-এর মধ্যে শুধু true অথবা false রাখতে পারবি। অর্থাৎ এগুলো হচ্ছে সিম্পল ডাটা টাইপ, প্রাথমিক বা primitive ডাটা টাইপ।

এখন আমরা primitive সম্পর্কে জানলাম, এটার অপজিট আরেকটা জিনিস রয়েছে, সেটা হচ্ছে non-primitive (এখানে তুই মাল্টিপল বা একের অধিক ভ্যালু রাখতে পারবি)। সেগুলো হলো Object, Array।

আরেকটা জিনিস সম্পর্কে জানি—

javascript
  let x = 5;
  let y = x;
  console.log(x, y);
  y = 7;
  console.log(x, y);

Output:
5 5
5 7

এখানে আমরা যে কাজটা করেছি, সেটা হলো reassign করা। অর্থাৎ একটা ভ্যারিয়েবলের মান আমরা পুনরায় সেটার মধ্যে আরেকটা ভ্যালু assign করেছি। এখানে x-এর জন্য আলাদা একটা মেমোরি স্পেস তোর কম্পিউটারে সেভ হয়েছে, সিমিলার y-এর জন্য আলাদা মেমোরি স্পেস সেভ হয়েছে। যখন y-এর মান চেঞ্জ করেছি, তখন শুধু y-এর মেমোরি স্পেসে ভ্যালু চেঞ্জ হয়েছে, x-এর কোনো পরিবর্তন হয়নি।

এতক্ষণ তো আমরা primitive নিয়ে কাজ করছিলাম, এখন non-primitive নিয়ে কিছু ব্যতিক্রম কিছু আছে কি না, সেটা জানার চেষ্টা করি।

javascript
  let p = {
    job: 'web developer'
  };
  let q = p;
  console.log(p, q);

Output:
{ job: 'web developer' } { job: 'web developer' }

এতটুকু পর্যন্ত তো সবকিছু ঠিকঠাকই আছে। এখন re-assign করে দেখি—

  let p = {
    job: 'web developer'
  };
  let q = p;
  console.log(p, q);
  q = {
    job: 'backend developer'
  };

Output:
{ job: 'web developer' } { job: 'web developer' }

এখানেও কোনো সমস্যা দেখছি না, primitive-এর মতোই কাজ করছে। আমরা যখন reassign করেছি, তখন আলাদা ভ্যালু সেট হয়েছে।

Reference Types

তুই যদি একটা ভেরিয়েবল, যেটার মান একটা অবজেক্ট। সেই ভেরিয়েবলের মান অন্য আরেকটা ভেরিয়েবলে সেট করিস, তারপর যেকোনো একটা অবজেক্টের প্রোপার্টির মান চেইঞ্জ করিস বা নতুন কোনো প্রোপার্টি যোগ করস, তাহলে কী হবে?

তাহলেও দুইটার মধ্যেই সেই প্রোপার্টির মান চেইঞ্জ হয়ে যাবে। যদিও তুই একটা ভেরিয়েবলের মধ্যে চেইঞ্জ করছস। সেটা দুই জায়গায় চেইঞ্জ হয়ে যাবে।

javascript
let p = {
  job: "web developer",
};
let q = p;
console.log(p, q);
q.job = "front-end developer";
console.log(p, q);

Output: {
  job: "web developer";
}
{
  job: "web developer";
}
{
  job: "front-end developer";
}
{
  job: "front-end developer";
}

কেন এমন হলো? আমি তো শুধু q অবজেক্টের মান চেঞ্জ করেছিলাম, কিন্তু p অবজেক্টের কেন মান চেঞ্জ হয়ে গেল?

এটা হওয়ার কারণ হচ্ছে— আমরা যখন কোনো অবজেক্টকে অন্য আরেকটা ভেরিয়েবলে সেট করি, তখন নতুন কোনো জিনিস বানায় না; বরং আগের জিনিসটার এক্সেস দিয়ে দেয়।

অনেকটা ধর, তুই কাউকে তোর বাসার চাবি দিলি। হয়তো তোর হাতে যে চাবি আছে, সেটার একটা কপি বানিয়ে দিলি। তাহলে সে কিন্তু নতুন কোনো বাড়ির এক্সেস পাচ্ছে না; বরং সে একই বাড়িতে ঢুকার সুযোগ পাচ্ছে। এখন সে যদি সেই বাড়িতে ঢুকে কিছু অগুছালো করে যায়, সেটা কিন্তু তুই বাড়িতে ঢুকলেও দেখতে পারবি।

এইবার যদি কোডিংয়ের ভাষায় বলি, তাহলে বলতে হবে— non-primitive কোনো মান যদি অন্য কোনো ভেরিয়েবলে সেট হয়, তখন সেটা reference টা ধরে রাখে এবং সেইম জিনিসের এক্সেসই দুই জায়গায় থাকে।

Pass By Value

যখন কোনো primitive টাইপ (যেমন: Number, String, Boolean) পাস করিস, সেটা ফাংশনে pass by value হয়। এর মানে হলো, ভ্যারিয়েবলের আসল মানের কপি ফাংশনের মধ্যে পাস করা হয়। তারপর ফাংশনের ভিতরে সেই প্যারামিটারের মান চেইঞ্জ করা হলেও যে ভেরিয়েবলকে ফাংশনের ভিতরে পাঠানো হলো, সেটার মান চেইঞ্জ হবে না।

javascript
  function changeValue(num) {
    num = 20;
    console.log('Inside function:', num);
  }

  let x = 10;
  console.log('Before function call:', x);
  changeValue(x);
  console.log('After function call:', x);

Output:
Before function call: 10
Inside function: 20
After function call: 10

এখানে x-এর মান ফাংশনের মধ্যে num-এ কপি করা হয়েছে। যখন num = 20 করা হলো, সেটা শুধু ফাংশনের ভেতরে পরিবর্তিত হয়েছে। x-এর আসল মান একই থেকে গেছে। কারণ, primitive টাইপ pass by value হয়।

Pass by Reference

যখন কোনো ফাংশনে non-primitive টাইপ (যেমন: Object, Array) পাস করিস, সেটা ফাংশনে pass by reference হয়। এর মানে হলো, ভ্যারিয়েবলের reference (অর্থাৎ, মেমোরি লোকেশন) পাস করা হয়। ফলে ফাংশনের ভিতরে ভ্যারিয়েবলটি চেইঞ্জ করলে আসল ভ্যারিয়েবলও চেইঞ্জ হয়। এইটা মাঝেমধ্যে ঝামেলা করে ফেলে। তাই এই বিষয়ে মাঝেমধ্যে বাগ হয়ে যায়।

javascript
  function updateJob(person) {
    person.job = 'designer';
    console.log('Inside function:', person);
  }

  let employee = { job: 'developer' };
  console.log('Before function call:', employee);
  updateJob(employee);
  console.log('After function call:', employee);

Output:
Before function call: {job: 'developer'}
Inside function: {job: 'designer'}
After function call: {job: 'designer'}

এখানে employee অবজেক্টের reference person-এ পাস করা হয়েছে। যখন person.job = 'designer' করা হলো, সেটা আসল employee অবজেক্টেও পরিবর্তিত হয়েছে। কারণ, non-primitive টাইপ pass by reference, অর্থাৎ একই মেমোরি স্পেস শেয়ার করে।

ফাইনাল কথা হচ্ছে, Primitive টাইপ ভ্যালুর কপি পাস করে। কোনো কানেকশন রাখে না। আর Non-Primitive টাইপ রেফারেন্স পাস করে। কানেকশন ধরে রাখে।

Practice:

  1. একটি ভ্যারিয়েবল বানা, যার নাম হবে num এবং সেটাতে 15 রাখ। আরেকটি ভ্যারিয়েবল বানা, যার নাম হবে copy এবং num-এর মান সেট কর। এবার copy-এর মান পরিবর্তন করে 25 কর। এরপর console.log-এ num এবং copy-এর মান দেখ। দুইটা কি সেইম নাকি আলাদা আলাদা।
  2. একটি অ্যারে তৈরি কর, যার নাম হবে arr এবং সেটাতে [1, 2, 3] থাকবে। সেটি আরেকটি ভ্যারিয়েবলে সেট কর। কপি করা ভ্যারিয়েবলে 88 যোগ কর। তারপর প্রথম এবং কপি করা অ্যারে কনসোল লগ করে দেখ, দুইটা কি ডিফারেন্ট আউটপুট দেখাচ্ছে?
  3. একটি অবজেক্ট বানা, যার নাম হবে language, যার মধ্যে দুটি প্রোপার্টি থাকবে name এবং age। name হবে JS এবং age হবে 30। language অবজেক্টটি একটি নতুন ভ্যারিয়েবল newVersion-এ সেট কর। এরপর newVersion-এ নতুন প্রোপার্টি যোগ কর, যেটার key হবে location এবং value হবে Browser। console.log ব্যবহার করে person এবং newPerson-এর মান দেখাও।

15-3: ডিফাইনহীন Undefined

লাইফের সবকিছুই যে ঠিকঠাক ফিটফাট থাকবে, এমন কোনো কথা নাই। মাঝেমধ্যে একটু ঘাঁটতে গেলে দেখবি, জিনিসপত্র ঠিকমতো ডিফাইন করা নাই। এই যে ঠিক করা নাই বা ডিফাইন করা নাই, এইটাকে ইংরেজিতে undefined বলতে পারস এবং এই undefined জিনিসটা মাঝেমধ্যে জাভাস্ক্রিপ্টে দেখতে পাবি। কারণে-অকারণে বা অনেক সিচুয়েশনেই undefined হতে পারে। তার মধ্যে আমি কয়েকটা কারণ বলতেছি—

  1. যখন তুই কোনো ভেরিয়েবল ডিক্লেয়ার করস, কিন্তু সেটাতে কোনো মান সেট করস নাই, তখন ডিফল্টভাবে ভেরিয়েবলটির মান undefined থাকে।
javascript
let first;
console.log(first);

Output: undefined;
  1. যখন কোনো ফাংশন রিটার্ন স্টেটমেন্ট থাকে না, তখন ফাংশনটি অটোমেটিকভাবে undefined রিটার্ন করে।
javascript
function second(a, b) {
  const total = a + b;
}
console.log(second());

Output: undefined;
  1. যদি তুই কোনো ফাংশনে কম প্যারামিটার পাঠাস, তাহলে যে প্যারামিটারগুলো দেস নাই, সেগুলার মান undefined হয়ে যায়।
javascript
  function third(a, b, c, d) {
    console.log(a, b, c, d);
  }
  third(2, 5);

Output: 2 5 undefined undefined
  1. যদি কোনো ফাংশনের রিটার্ন স্টেটমেন্ট থাকে, কিন্তু রিটার্নের পর কোনো ভেরিয়েবল বা কোনো মান না থাকে, তাহলে সে undefined রিটার্ন করে।
javascript
function noNegative(a, b) {
  if (a < 0 || b < 0) {
    return;
  }
  return a + b;
}
console.log(noNegative(2, -5));

Output: undefined;
  1. যদি কোনো অবজেক্টে এমন একটা প্রোপার্টি খুঁজিস, যেটা আসলে অবজেক্টের মধ্যে নাই, তাহলে সেই প্রোপার্টির মান undefined হয়।
javascript
const fifth = { id: 2, name: "ponchom", age: 40 };
console.log(fifth.salary);

Output: undefined;
  1. অ্যারের মধ্যে যে ইনডেক্সে উপাদান নাই, সেই ইনডেক্সে গিয়ে উপাদান খুঁজিস, তাহলে বলবে, সেই ইনডেক্সে উপাদান বা মান ডিফাইন করা নাই। অর্থাৎ undefined পাবি। যেমন নিচের অ্যারের মধ্যে 5 টা উপাদান আছে। অর্থাৎ 0 থেকে শুরু করে 4 ইনডেক্স পর্যন্ত উপাদান আছে। এখন তুই যদি 51 ইনডেক্সের উপাদান খুঁজতে চাস, সে তোকে দিতে পারবে না। কারণ, সেই ইনডেক্সে কোনো কিছু নাই। তাই সে undefined দিয়ে বুঝাবে, সেই ইনডেক্সে উপাদান ডিফাইন করা হয় নাই।
javascript
const sixth = [4, 89, 87, 56, 54];
console.log(sixth[51]);

Output: undefined;
  1. যখন অ্যারের কোনো এলিমেন্ট ডিলিট করিস, সেই index-এর উপাদানের মান undefined হয়ে যায়।
javascript
const seventh = [8, 9, 7, 6, 4];
delete seventh[1];
console.log(seventh[1]);

Output: undefined;
  1. তুই যদি সরাসরি কোনো ভেরিয়েবলকে undefined দিয়ে সেট করিস, সেটিও সম্ভব।
javascript
const eighth = undefined;

null vs undefined:

undefined-এর মানে বুঝায় ডিফাইন করা নাই বা ডিফাইন করা হয়নি। অন্য দিকে কোনো কিছু নাই, যদি ইচ্ছা করে বুঝাইতে চাই, সেক্ষেত্রে আরেকটা জিনিস আছে, সেটাকে null বলে। তবে null-এর undefined-এর মধ্যে কিছু ডিফারেন্স আছে।

javascript
console.log(typeof undefined);
console.log(typeof null);

Output: undefined;
object;

null-এর টাইপ object, যা একটি JavaScript-এর পুরনো ভুল। এটি ঠিক করা হয়নি। কারণ, চেইঞ্জ করতে গেলে অনেক ওয়েবসাইটে যেসব কোড লিখে রাখছে, সেগুলা সব হুট করে এরর দিতে শুরু করতে পারে।

কখনোই নিজে থেকে undefined দিয়ে ভ্যালু সেট করা উচিত না, এর পরিবর্তে null ব্যবহার করবি। undefined সাধারণত সিস্টেমের মাধ্যমে ব্যবহার হয়, আর null ইচ্ছাকৃতভাবে ভ্যালুর অভাব বোঝাতে ব্যবহৃত হয়।

Practice:

  1. তুই একটা ভ্যারিয়েবল ডিক্লেয়ার করলি, কিন্তু কোনো ভ্যালু দিলি না। এবার সেটা কনসোলে প্রিন্ট কর আর দেখ, কী আসে।
  2. তোর একটা ফাংশন আছে, যা দুইটা নাম্বার যোগ করে, কিন্তু কোনো রিটার্ন স্টেটমেন্ট নেই। এখন ওই ফাংশনটাকে কল করে কনসোলে দেখ, রিটার্ন কী আসছে।
  3. [10, 20, 30, 40, 50]। তারপর এর মধ্যে থেকে 2 ইনডেক্স (যেটাতে 30 আছে) এলিমেন্টটি ডিলিট কর। পরে ঐ ইনডেক্সে গিয়ে দেখ, কীরকম আউটপুট আসে।
  4. যোগ করার একটা ফাংশন লিখে ফেলেছিস function sum(a, b, c) { }। আর এই ফাংশনকে কল করছস sum(5, 10); এইভাবে। এইবার ফাংশনের ভিতরে কনসোল লগ করে দেখ, থার্ড প্যারামিটারের মান কী। কেন সেটা হয়েছে।
  5. const student = { name: "serious sojib", roll: 1, masks: 99 }; এই অবজেক্ট থেকে student-এর marks পেতে গেলে কত পাবি। সেটা কেন পাচ্ছস?

15-4: পল্টিবাজ নট (!), ডাবল দিলে কট

দুনিয়াতে কে যে ভালো, আর কে যে খারাপ— সেটা চেহারা দেখে বুঝা যায় না। পোশাক-আশাক দেখেও বুঝা যায় না। দেখা যায় ভিতরটা দেখে। পরীক্ষা-নিরীক্ষা করে। জাভাস্ক্রিপ্টেও মাঝেমধ্যে কনফিউজড হয়ে যাবি, কে যে true টাইপের, আর কে যে false টাইপের। কারণ, একটা প্রোগ্রামিং ল্যাঙ্গুয়েজে অনেক ধরনের জিনিস আছে। কিছু পজিটিভ, আবার কিছু নেগেটিভ। যেগুলা true টাইপের, তাদেরকে truthy বলে, আর যেগুলা false টাইপের, সেগুলাকে falsy বলে।

এইগুলা নিয়ে আমি কয়েকটা বলে দিচ্ছি। এগুলা মুখস্থ করার দরকার নেই। জাস্ট দেখে রাখ—

Truthy:

কোনো কিছুর ভ্যালু যদি সত্য হয়, তাহলে সেটা অবশ্যই true হবে।

সংখ্যার মধ্যে শূন্য ছাড়া অন্য যেকোনো পজিটিভ সংখ্যা ( 0-এর চাইতে বড় যেকোনো সংখ্যা) বা নেগেটিভ সংখ্যা (0-এর চাইতে ছোট যেকোনো সংখ্যা ) সত্য বা true হিসেবে আচরণ করবে।

javascript
  const x = -4;
  if (x) {
    console.log("value of x is truthy");
  } else {
    console.log("value of x is falsy");
  }

Output: value of x is truthy

empty string ('') হলে সেটা false হবে। আর empty string ('') ছাড়া যেকোনো string সত্য হবে। এমনকি যদি একটা ক্যারেক্টার থাকে বা কোটেশনের মধ্যে যদি স্পেসও থাকে (' '), তাহলে সেটা true হবে।

javascript
  const x = "a";
  if (x) {
    console.log("value of x is truthy");
  } else {
    console.log("value of x is falsy");
  }

Output: value of x is truthy

যদিও 0-কে false ধরবে। তারপরেও 0 যদি কোটেশনের মধ্যে থাকে, তাহলে সে কিন্তু সংখ্যা না; বরং একটা স্ট্রিং হয়ে গেছে। আবার সে কিন্তু খালি স্ট্রিং না; বরং কোটেশনের মধ্যে কিছু একটা আছে। তাই এইটা কিন্তু true হবে। অর্থাৎ 0 হবে false, কিন্ত '0' হবে true।

একইভাবে false নিজে false, তবে false যদি স্ট্রিং আকারে থাকে, 'false' তাইলে সে কিন্তু স্ট্রিং। এবং এম্পটি স্ট্রিং না, তাই 'false' কিন্তু সত্য হবে।

Empty object { } এবং empty array [ ] দুটোই true হিসেবে গণ্য হয়।

Falsy:

  • কোনো কিছুর ভ্যালু যদি মিথ্যা হয়, তাহলে সেটা false হবে।
  • কোনো ভেরিয়েবলের মান যদি শূন্য(0) হয়, সেটা false হবে।
  • Empty string হলে সেটা false হবে।
  • কোনো কিছু যদি undefined থাকে, তখন সেটা false হবে।
  • null ভ্যালুও false হয়।

পল্টিবাজ নট (!)

অনেক আগে তুই পল্টিবাজ ! (নট) অপারেটর দেখছিলি। সে ভ্যালুটার উল্টো (inverse) করে। এইটাকে লজিক্যাল নট বলে। এটা কোনো কিছু true হলে তাকে false করে দেবে, আর false হলে তাকে true করে দেবে।

আর তুই যদি কোনো কিছুর falsy অবস্থা চেক করতে চাস, যেমন কোনো ভ্যালু false হলে তারপর শর্তের ভিতরে ঢুকতে চাস, সেক্ষেত্রে if-এর পরে শর্তের মধ্যে false হতে পারে, এমন ভেরিয়েবল বা ভ্যালুর আগে not (!) চিহ্ন দিয়ে দিবি। তাহলে সেই ভ্যালু false বা falsy কিছু হলে সে শর্তের ভিতরে চলে যাবে।

javascript
  const y = '';
  if(!y) {
      console.log('value of y is falsy');
  }

Output: value of y is falsy

ডাবল নট !!

একটা নট দিলে পল্টি মারে। দুইটা নট দিলে কী হবে? দুইবার পল্টি মারবে। অর্থাৎ প্রথম নট একবার উল্টাবে, সেকেন্ড নট আবার উল্টাবে। এটাই ডাবল নটের মূল কনসেপ্ট— তুই যে ভ্যালু দিলি, সেটা Boolean হিসেবে true না false, সেটা বের করে।

javascript
console.log(!!"hello"); // true
console.log(!!42); // true
console.log(!!{}); // true
console.log(!![]); // true

console.log(!!""); // false
console.log(!!0); // false
console.log(!!null); // false
console.log(!!undefined); // false
console.log(!!NaN); // false

ডাবল নট দিয়ে যেকোনো ভ্যালুকে Boolean টাইপে (true, false) খুব সহজে কনভার্ট করা যায়।

javascript
const value = "coding";
console.log(!!value); // true

!! দিয়ে তুই চট করে দেখে নিতে পারবি, কোনো ভ্যালু truthy নাকি falsy।

javascript
const value = 0;
if (!!value) {
  console.log("Truthy");
} else {
  console.log("Falsy");
}

Output: Falsy;

সহজভাবে বললে—

! – একবার উল্টো করে।

!! – দুইবার উল্টো করে আসল অবস্থায় ফেরায় Boolean হিসেবে।

Practice:

  1. একটা if কন্ডিশনের মধ্যে 'false' লিখে চেক কর। 'false' কি সত্য টাইপের ভ্যালু হিসেবে আচরণ করে, নাকি করে না।
  2. একটা খালি অবজেক্টে { } কোনো প্রোপার্টি নাই। এইটা কী falsy একটা ভ্যালু। চেক কর if-এর ভিতরে শর্ত হিসেবে লিখে।
  3. একটা খালি অ্যারে [ ] কোনো উপাদান নাই। এইটা কি truthy ভ্যালু। চেক কর if-এর ভিতরে শর্ত হিসেবে লিখে।
  4. একটা অ্যারে [ ] আছে, এইটার সামনে !! দিলে কী পাওয়া যাবে।

15-5: == এর বেইজ্জুতি Type coercion

যেহেতু সমান সমান তুলনা করতে দুইটা সমান চিহ্ন আবার তিনটা সমান চিহ্ন ইউজ করা যায়, তাই এই দুইটার মধ্যে কিছু ডিফারেন্স আছে। আবার কিছু কমন জিনিসও আছে। আর এইগুলার কারণে কিছু প্যাঁচাপেঁচি হয়ে যায়। তবে ভিতরের কারণ জানলে বিষয়টা অনেক ফকফকা হয়ে যাবে।

ট্রিপল ইকুয়াল ঠিক কথা বলবে। সে বলবে—

2 আর '2' সেইম না। 1 আর true সেইম না। null আর undefined সেইম না। এম্পটি স্ট্রিং ('') আর 0) সেইম না। false আর '0' সেইম না। [ ] আর false সেইম না।

এইগুলা সব আলাদা আলাদা টাইপের জিনিস এবং আলাদা আলাদা মান। তাই নিচের সবগুলা false আসবে।

javascript
  console.log(2 === '2');
  console.log(1 === true);
  console.log(null === undefined);
  console.log('' === 0);
  console.log(false === '0');
  console.log([ ] === false);

Output: false false false false false false

মজার বিষয় হচ্ছে, ওপরের সবগুলা কোম্পারিজন (তুলনা) যদি তুই ডাবল ইকুয়াল দিয়ে করতে যাস, সবগুলা true আসবে।

javascript
console.log(2 == "2");
console.log(1 == true);
console.log(null == undefined);
console.log("" == 0);
console.log(false == "0");
console.log([] == false);

ডাবল ইকুয়াল এমন আকাম করে। সে কোন আক্কেলে বলে, খালি স্ট্রিং আর 0 সেইম কিংবা খালি array আর false সেইম। অথচ একটু আগে দেখছস [ ]-এর মান truthy, আর এখন বলতেছে, এইটা false-এর সমান। কী একটা বেইজ্জুতি অবস্থা।

খুব সিম্পলভাবে দেখলে দুইটার মধ্যে পার্থক্য হচ্ছে, ট্রিপল ইকুয়াল মান এবং টাইপ দুটোই চেক করে। এ জন্যই সব সময় ট্রিপল ইকুয়াল চিহ্ন (===) ইউজ করলে টাইপ যদি আলাদা হয়, মান সেইম হলেও সে সেইম বলবে না। অন্যদিকে ডাবল ইকুয়াল ভেজাল করে (ভেজালের মধ্যেও লজিক আছে), আর সেজন্য ডাবল ইকুয়াল ইউজ করতে বেশির ভাগ সময় নিষেধ করে।

ডাবল ইকুয়ালের নেপথ্যে কলকাঠি কে নাড়ে?

ডাবল ইকুয়াল == ব্যবহারের সময় জাভাস্ক্রিপ্ট একটি ধাপে ধাপে প্রসেসের ভিতর দিয়ে যায়, যেটা টাইপ কনভার্সন (type coercion) নামে পরিচিত। এর মানে হচ্ছে, যদি দুইটা ভিন্ন টাইপের মান তুলনা করতে বলা হয়, জাভাস্ক্রিপ্ট ভিতরে ভিতরে দুইটাকে এক টাইপে কনভার্ট করে তারপর তুলনা করে।

টাইপ কনভার্সন প্রক্রিয়া। নিচে এক ধাপ এক ধাপ দেখানো হলো, কীভাবে কাজটা হয়—

প্রথমে জাভাস্ক্রিপ্ট চেক করে, ডাবল ইকুয়ালের দুই সাইডে দুইটা ভ্যালুর টাইপ কি এক? যদি এক হয়, তাহলে মান চেক করে মান সেইম হলে true বলে দেয়। আর মান সেইম না হলে false বলে দেয়।

আর যদি ডাবল ইকুয়ালের দুইপাশে মান দুই টাইপের হয়, তাহলে জাভাস্ক্রিপ্ট ভিতরে ভিতরে আগে টাইপ কনভার্সন করবে। তারপর দুই সাইডের মান একই টাইপের করে তারপর তুলনা করবে।

যদি একপাশে নাম্বার আর অন্যপাশে স্ট্রিং থাকে, তাহলে স্ট্রিংটাকে নাম্বারে কনভার্ট করে তারপর তুলনা করে। যেমন, 2 == '2' তুলনা করার সময় '2'-কে আগে সংখ্যায় রূপান্তর করে সংখ্যায় বানায়। সেখানে 2 পায়, তারপর 2 আর 2 তুলনা করে। আর এখন যেহেতু দুই সাইডে মান সেইম হয়ে গেছে, তাই true বলবে।

একইভাবে 2 == '20' তুলনা করতে গিয়ে প্রথমে '20'-কে সংখ্যায় রূপান্তর করবে। তারপর 2 আর 20 তুলনা করে দেখবে মান আলাদা। তাই বলবে, এই দুইটা সেইম না, অর্থাৎ false বলবে।

প্রসেসটা তো বুঝতে পারছস?

ডাবল ইকুয়ালের একপাশে বুলিয়ান, আর আরেকপাশে সংখ্যা থাকলে বুলিয়ানকে সংখ্যায় রূপান্তর করে। আর বুলিয়ানকে সংখ্যায় রূপান্তর করতে গেলে true থাকলে সেটা সংখ্যায় 1 হয়, আর false থাকলে সেটা সংখ্যায় 0 হয়। তারপর বাকি সংখ্যার সাথে তুলনা করে।

এই কারণে false == '0'-এর মান সত্য হয়। কারণ, false কনভার্ট হয়ে 0 হয়, তারপর বামপাশে 0 সংখ্যা আর ডানপাশে '0' স্ট্রিং। তখন ওপরের তিন নম্বর রুল অনুসারে স্ট্রিংকে সংখ্যায় রূপান্তর করে 0-এর সাথে 0 তুলনা করে। আল্টিমেটলি সত্য (true) হয়।

সিমিলার স্টাইলেই জাভাস্ক্রিপ্ট বের করার চেষ্টা করে। false == '1', বা true == '1', বা true == '10' কোনোটার তুলনার মান সত্য হবে, আর কোনোটার তুলনার মান মিথ্যা হবে।

যদি ডাবল ইকুয়ালের একপাশে object, আর অন্য পাশে কোনো প্রিমিটিভ ডাটা টাইপ (string, number, boolean), তাহলে প্রথমে object-কে তার প্রিমিটিভ ভ্যালুতে কনভার্ট করবে। তখন স্ট্রিংয়ে কনভার্ট করার জন্য toString ফাংশন ব্যবহার করতে পারে।

যেমন ধর, [ ] == 0 এই তুলনা করার জন্য একপাশে [ ] (খালি অ্যারে, যা একটি object) এবং অন্যপাশে 0 (একটি number)। যেহেতু টাইপ দুইটার ভিন্ন (object এবং number), জাভাস্ক্রিপ্ট টাইপ কনভার্সন করবে। যখন object (যেমন [ ])-কে প্রিমিটিভ ভ্যালুতে কনভার্ট করার চেষ্টা করে, এটি কনভার্সন করার জন্য toString() কল করে এবং [ ].toString() খালি স্ট্রিং ("") রিটার্ন করে। এখন তুলনাটি দাঁড়ায়: "" == 0 এ। অর্থাৎ একপাশে খালি স্ট্রিং ("") এবং অন্যপাশে সংখ্যা (0)। এইখানেও টাইপ দুইটা ভিন্ন, তাই আবার টাইপ কনভার্সন করবে। খালি স্ট্রিং ("")-কে নাম্বারে কনভার্ট করলে 0 হয়। তারপর তুলনাটি দাঁড়ায়: 0 == 0 তে। তখন যেহেতু দুইটা ভ্যালুই এখন সংখ্যা (0) এবং একই মান হওয়ায় জাভাস্ক্রিপ্ট true রিটার্ন করে।

একই সিস্টেমে [ 1 ] == 1 তুলনা করার জন্য একপাশে [ 1 ] (অ্যারে, যা একটি object) এবং অন্যপাশে 1 (একটি number)। যেহেতু টাইপ দুইটার ভিন্ন (object এবং number), জাভাস্ক্রিপ্ট টাইপ কনভার্সন করবে। যখন object (যেমন [ 1 ] )-কে প্রিমিটিভ ভ্যালুতে কনভার্ট করার চেষ্টা করে।

এটি কনভার্সন করার জন্য toString() কল করে এবং [1 ].toString() স্ট্রিং ("1") রিটার্ন করে। এখন তুলনাটি দাঁড়ায়: "1" == 1 এ। অর্থাৎ একপাশে খালি স্ট্রিং ("1") এবং অন্যপাশে সংখ্যা (1)। তখন ওপরের কনভার্সন অনুসারে স্ট্রিং সংখ্যায় রূপান্তর হয়ে 1 == 1 হবে এবং true রিটার্ন করবে।

এইবার বুঝছস, কীভাব জাভাস্ক্রিপ্ট মনে করে, একটা [ ] (খালি অ্যারে) কীভাবে 0-এর সমান হয়।

অর্থাৎ জাভাস্ক্রিপ্টে যত অদ্ভুত জিনিস আছে, তার সব কিছুরই লজিক্যাল ব্যাখ্যা আছে।

null == undefined তুলনা করার সময় জাভাস্ক্রিপ্ট কোনো টাইপ কনভার্সন হয় না। শুধু এই দুটি ভ্যালুকে জাভাস্ক্রিপ্ট নিজে থেকেই সমান ধরে নেয়। তবে যখন null === undefined দিয়ে চেক করে, তখন === (স্ট্রিক্ট ইকুয়ালিটি) ব্যবহার করা হয়, তখন টাইপ এবং মান দুটোই চেক করা হয়। তাই null এবং undefined-এর টাইপ ভিন্ন হওয়ায় null === undefined-এর জন্য false রিটার্ন করে।

NaN == NaN কেন false হয়, সেটা বুঝতে গেলে প্রথমেই চিন্তা করতে হবে, জাভাস্ক্রিপ্ট অদ্ভুত হলেও লজিক্যাল নিয়মে চলে এবং NaN মানে "Not a Number" হলেও মজার বিষয় হচ্ছে, এটি নিজেই একটি number টাইপ। এ ছাড়া Not a Number-এর মানে হলো, এইটা কোনো স্পেসিফিক বা নির্দিষ্ট মান না। অর্থাৎ এইটার মান নির্দিষ্ট না। তাই একটা অনির্দিষ্ট মান আরেকটা অনির্দিষ্ট মানের সমান বা সেইম, সেটা বলা যাবে না। কারণ, একটা অনির্দিষ্ট মান যেকোনো কিছু হতে পারে। আবার অন্য আরেকটা অনির্দিষ্ট মান অন্য যেকোনো কিছুই হতে পারে। তাই এই দুইটা সেইম না। তবে কখনো NaN চেক করতে চাইলে isNaN(NaN) ফাংশনকে কল করবি। তাহলে সে বলে দিবে NaN কি না।

ডাবল ইকুয়াল দরকার হলে টাইপ কনভার্সন করে, যা নতুন প্রোগ্রামারদের জন্য কনফিউজিং হতে পারে। এজন্যই === ব্যবহার করা ভালো। কারণ, এটি টাইপ এবং মান দুটোই চেক করে এবং কোনো কনভার্সন করে না।

Non-Primitive Comparison

আরেকটা বিষয়, যদি দুইটা object কম্পেয়ার করতে যাস—

javascript
console.log({} == {});

Output: false;

তুই ভাবছিস, কেন এমন হলো? দুইটা দেখতে একই, কিন্তু আউটপুট false কেন? এটা শুধু object না, array-এর ক্ষেত্রেও একই হবে। কারণ, যখন তুই non-primitive (object/array) কম্পেয়ার করিস, তখন তারা ভ্যালু না; বরং reference চেক করে。

javascript
const first = {};
const second = first;

console.log(first === second);

Output: true;

এখানে দেখ, second ভেরিয়েবল first-এর reference ধরে রেখেছে, তাই আউটপুট true এসেছে। Non-primitive টাইপের ক্ষেত্রে reference চেক হয়, মান চেক হয় না।

Practice:

  1. তুই একটা প্রোগ্রাম লিখে চেক কর, যদি true আর 1 ডাবল ইকুয়াল হয়, তবে "same" দেখাবে, আর না হলে "different" দেখাবে।
  2. তুই দুইটা আলাদা object তৈরি কর, তারপর ট্রিপল ইকুয়াল দিয়ে চেক কর, তারা সমান কি না। যদি সমান না হয়, তাহলে কেন, তা চিন্তা কর।
  3. একটা array তৈরি কর, তারপর আরেকটা ভেরিয়েবলে ওই array টাকে reference হিসেবে রাখ। এবার ট্রিপল ইকুয়াল দিয়ে চেক কর, এই দুইটা সমান কি না।
  4. একটা ফাংশন লিখ, যেখানে প্রথম প্যারামিটার হলো, কোনো সংখ্যা আর দ্বিতীয় প্যারামিটার হলো boolean। ফাংশনটা চেক করবে, এই দুইটা মান ডাবল ইকুয়াল কি না, আর আউটপুট দেখাবে।
  5. খালি string এবং false সমান কি না, সেটা ডাবল ইকুয়াল দিয়ে চেক করে দেখ।
  6. null এবং undefined-কে ট্রিপল ইকুয়াল দিয়ে তুলনা কর এবং এর আউটপুট কী হয়, তা দেখ।
  7. 1 == '1' চেক কর এবং জাভাস্ক্রিপ্ট কীভাবে টাইপ কনভার্সন করে, তা ব্যাখ্যা কর।

15-6: কল মি callback

ফাংশন একটা সর্বভুক পেটুক। তাকে যা দিবি, তাই প্যারামিটার হিসেবে খেয়ে ফেলবে। বিশ্বাস হচ্ছে না। একটু চিন্তা করে দেখ, ফাংশনে তুই যদি প্যারামিটার হিসেবে সংখ্যা, বুলিয়ান, স্ট্রিং পাঠাস, সে নিয়ে নিবে। প্যারামিটার হিসেবে অ্যারে ও অবজেক্ট পাঠালে সেটাকেও নিয়ে নিবে। এমনকি প্যারামিটার হিসেবে আরেকটা ফাংশন পাঠালে সেটাকে সে খেয়ে দিবে।

এইটাই বাস্তবতা!

ধর, তোর কাছে greeting নামে একটা ফাংশন আছে। সেটার একটা প্যারামিটার আছে greatingHandler, এইটা যেকোনো কিছু হতে পারে এবং প্যারামিটার হিসেবে যেটাই পাঠাক না কেন, তুই সেটাকে কনসোল লগ করতেছস। তারপর নিচে আরেকটা ফাংশন আছে morningGreet নামে। তুই যদি এইটাকে প্যারামিটার (আর্গুমেন্ট) হিসেবে greeting ফাংশনকে কল করিস, তাহলে আউটপুটে বলে দিবি, তুই একটা ফাংশনকে সেন্ড করেছিস।

javascript
  function greeting(greatingHandler) {
    console.log(greatingHandler);
  }

  function morningGreet() {
    console.log('Good Morning');
  }

  greeting(morningGreet);

Output:
[Function: morningGreet]

কোনো এক মনের দুঃখে যদি তুই প্যারামিটার হিসেবে একটা ফাংশন পাঠাস এবং সেটা যদি ফাংশন হয়ে থাকে, তাহলে ফাংশনের ভিতরে তাকে অবশ্যই কল করতে পারবি। কল করার জন্য তেমন কিছুই করা লাগবে না। ফাংশনের মধ্যে যে প্যারামিটার হিসেবে ডিক্লেয়ার করছস, সেটার নামের পরে দুইটা ব্র্যাকেট দিলেই কল করা হয়ে যাবে।

javascript
  function greeting(greatingHandler) {
    greatingHandler()
  }

  function morningGreet() {
    console.log('Good Morning');
  }

  greeting(morningGreet);

Output: Good Morning

এই জন্য আউটপুট হিসেবে Good Morning পাবি। কারণ, morningGreet নামে যে ফাংশনটা আর্গুমেন্ট হিসেবে সেন্ড করছস, সেটার ভিতরে এই Good Morning-কে কনসোল লগ করতেছস।

কলব্যাক ফাংশন হলো

Callback function হলো এমন একটি ফাংশন, যা অন্য ফাংশনের ভিতরে পাস করা হয় এবং সেই ফাংশনটি কিছু কাজ করার পর এই callback function-কে কল করে।

আরেকটা উদাহরণ দেই—

নিচের কোডে তুই calculate ফাংশন ডিক্লেয়ার করছস। সেটাতে তিনটা প্যারামিটার লাগবে— a, b, এবং callback। এদের মধ্যে a এবং b হলো সংখ্যাগুলো, যেগুলো যোগ করা হবে। আর callback হলো একটা ফাংশন, যেটা যোগফল (sum) নিয়ে কিছু একটা করবে।

javascript
  function calculate(a, b, callback) {
    const result = a + b;
    callback(result);
  }

এখন তোর কাছে আরেকটা ফাংশন আছে printResult, যে একটাই প্যারামিটার নেয় value নামে এবং সেই value-কে কনসোলে প্রিন্ট করে।

  function printResult(value) {
    console.log("Result is:", value);
  }

  calculate(5, 10, printResult);

Output: Result is: 15

এখন তুই যদি calculate(5, 10, printResult) কল করস, তাহলে calculate ফাংশনে গিয়ে a এবং b যোগ করে result হবে 15। তারপর আছে callback (result); অর্থাৎ callback ফাংশন, যেটা আসলে printResult ফাংশন (যেটাকে থার্ড প্যারামিটার হিসেবে পাঠানো হইছে) সেই ফাংশনকে কল করবে এবং result-এর মান অর্থাৎ 15 দিয়ে কল করবে। তখন সে printResult(15)-এর মতো printResult ফাংশনের ভিতরে গিয়ে console.log("Result is:", value) চালাবে আর কনসোলে লগ করবে Result is: 15।

অলরেডি কলব্যাক করেছি

তুই যখন map, filter, find ইত্যাদি মেথড ব্যবহার করিস, তখন যেই ফাংশনটা তুই তাদের ভিতরে পাঠাইস, সেটি আসলে callback function।

map মেথডে যেই ফাংশন পাস করা হয়, তা প্রতিটি উপাদানের জন্য কলব্যাক ফাংশনকে কল করে। একইভাবে filter মেথডে যেই ফাংশন পাস করা হয়, অর্থাৎ যে ফাংশন দিয়ে উপাদানগুলোকে ফিল্টার করে বা find করে, সেগুলা আসলে কলব্যাক ফাংশন।

javascript
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map((num) => num * 2);
console.log(doubledNumbers);

Output: [2, 4, 6, 8, 10];

আবার অ্যারো ফাংশনকে কলব্যাক ফাংশন হিসেবে ইউজ না করে নরমাল ফাংশনকে কলব্যাক ফাংশন হিসেবে ইউজ করতে পারবি।

javascript
const numbers = [1, 2, 3, 4, 5];
const result = numbers.map(function (num) {
  return num * 2;
});
console.log(result);

Output: [2, 4, 6, 8, 10];

Practice:

  1. একটি ফাংশন লিখ, তার ভিতরে আরেকটি ফাংশন প্যারামিটার হিসেবে পাঠিয়ে তাকে কল কর এবং রেজাল্ট দেখ।
  2. ফাংশন লিখ, যেটি একটি অবজেক্ট প্যারামিটার নিবে এবং callback-এর মাধ্যমে অবজেক্টের সব কিগুলো কনসোলে দেখাবে।
  3. numberProcessor নামে একটি ফাংশন লিখ, যেটি একটি সংখ্যা এবং একটি callback নিবে এবং সংখ্যাটিকে 5 দিয়ে ভাগ করে callback-এর মাধ্যমে আউটপুট দেখাবে।
  4. তুই ফাংশনের ভিতর থেকে অন্য একটা ফাংশন কল করবি, যেমন greeting ফাংশনের ভিতরে greetingHandler() ফাংশন কল করা হয়েছে। একবার তুই একটা প্রোগ্রাম লিখে দেখ, যেখানে প্রথম ফাংশনটি অন্য ফাংশনকে কল করবে এবং ফাংশনের আউটপুট কনসোলে দেখাবে।

15-7: ছোট scope-এ বড় কথা !

ছোটবেলায় যখন তুই দুই মাসের বাচ্চা ছিলি, তখন তোর স্কোপ ছিল শুধু খাটের ওপর পর্যন্ত, অর্থাৎ তুই খাটের ওপরেই গড়াগড়ি করতে পারতি। একটু বড় হওয়ার পর তুই শুধু বাসার ভেতরে দৌড়াদৌড়ি করতে পারতি। আবার আরও বড় হয়ে যখন স্কুলে ভর্তি হলি, তখন তোর স্কোপ ছিল স্কুল থেকে বাসা, বাসা থেকে স্কুল। আর যখন কলেজে উঠলি, তখন তুই স্কুল আর বাসার বাইরে প্রাইভেট পড়তে যেতে বা খেলাধুলা করতে পারতি। ফাইনালি ভার্সিটি উঠার পর তোর সামনে দুনিয়া খুলে গেছে। তুই যেখানে খুশি যেতে পারস। মাঝেমধ্যে গ্রুপ স্টাডি আছে বলে বিভিন্ন জায়গায় যেমন, বান্দরবান, সাজেক বা সুন্দরবন ঘুরতে চলে যাস।

অর্থাৎ বয়সের সাথে সাথে তোর স্কোপ বড় হতে শুরু করেছে।

প্রোগ্রামিংয়ের ক্ষেত্রেও স্কোপ নামে কিছু একটা জিনিস আছে এবং এটা সম্পর্কে তোকে কিছুটা জানার দরকার আছে।

  1. লোকাল স্কোপ

    • ব্লক স্কোপ
    • ফাংশন স্কোপ
  2. গ্লোবাল স্কোপ

  3. Lexical Scope

জাভাস্ক্রিপ্টে বিভিন্ন ধরনের ব্লক আছে। ব্লক বলতে সেকেন্ড ব্র্যাকেট { } দিয়ে সীমাবদ্ধ কোনো কোডকে বুঝায়। নিচে জাভাস্ক্রিপ্টের সমস্ত সম্ভাব্য ব্লকগুলোর উদাহরণ দিলাম—

javascript
  if (true) {
    let message = "This is inside an if block";
    console.log(message);
  }

Output: This is inside an if block

ওপরে if-এর পরে সেকেন্ড ব্র্যাকেটের ভিতরে কিছু কোড আছে। এইটাই একটা ব্লক।

নিচে for লুপের পরে সেকেন্ড ব্র্যাকেটের ভিতরে কিছু কোড আছে। এই সেকেন্ড ব্র্যাকেটের ভিতরের অংশটুকু আরেকটা কোড ব্লক।

javascript
  for (let i = 0; i < 3; i++) {
    let loopMessage = "This is inside a for loop block";
    console.log(loopMessage);
  }

Output: This is inside a for loop block

while লুপ, try-catch বা অন্য সব ক্ষেত্রেও কিন্তু সেকেন্ড ব্র্যাকেট দিয়ে বিভিন্ন ব্লক লেখা হয়।

javascript
let count = 0;
while (count < 5) {
  console.log(count);
  count++;
}

এমনকি একটা ফাংশন লিখলে সেকেন্ড ব্র্যাকেটের ভিতরে কিছু কোড থাকে, সেটাও একটা ব্লক। যদিও ফাংশনের এই কোড ব্লককে ফাংশন ব্লক বা অনেক সময় লোকাল স্কোপও বলে ফেলে।

javascript
function add(a, b) {
  const sum = a + b;
  return sum;
}

অর্থাৎ যেকোনো দুইটা সেকেন্ড ব্র্যাকেটের ভিতরে এক বা একাধিক লাইনের কোড থাকলে সেটাকে কোড ব্লক বলে।

ব্লক স্কোপ (Block Scope)

একটি কোড ব্লকের ভিতরে অর্থাৎ সেকেন্ড ব্র্যাকেটের ভিতরে যদি let বা const দিয়ে একটা ভেরিয়েবল ডিক্লেয়ার করা হয়, তখন সেটি ব্লক স্কোপে পড়ে। ব্লক স্কোপ বলতে বুঝায় ঐ ভেরিয়েবল শুধু ঐ স্কোপের মধ্যেই এক্সেস করা যাবে। ইউজ করা যাবে। দরকার অনুসারে চেইঞ্জ করা যাবে। তবে ওই ব্লকের বাইরে অর্থাৎ সেকেন্ড ব্র্যাকেটের বাহির থেকে ইউজ করা যাবে না। এক্সেস করা যাবে না। চেইঞ্জ তো করা যাবেই না।

javascript
const smart = true;
if (smart) {
  let message = "prochur gorom";
  console.log("Batas dao");
}

// trying to access outside of the block
console.log(message);

// ReferenceError: message is not defined

নোট: let এবং const দিয়ে ডিক্লেয়ার করা ভেরিয়েবলগুলো ব্লক স্কোপের মধ্যেই থাকবে এবং যেখানে ডিক্লেয়ার করা হবে, তার নিচেও ইউজ করা যাবে।

ফাংশন স্কোপ, লোকাল স্কোপ

ধর, আমরা একটা ফাংশন ডিক্লেয়ার করলাম। এই ফাংশনের যে প্যারামিটারগুলোকে (a, b) ফাংশনের ব্র্যাকেটের বাইরে গিয়ে কল করতে চাস বা কোনো কাজে লাগাতে চাস, তখন কিন্তু তুই ইরোর খেয়ে যাবি। নিচের উদাহরণে রান করলে দেখবি একটা ইরোর দেখাচ্ছে। অর্থাৎ ফাংশনের প্যারামিটারগুলো a, b-এর স্কোপ শুধু সেই ফাংশনের ভেতরেই থাকে। এমনকি ফাংশনের ভিতরে যে ভেরিয়েবলগুলো তুই ডিক্লেয়ার করিস, সেটাকেও তুই সেই ফাংশনের বাইরে পাবি না। যেমন, ওপরের ফাংশনের ভিতরে total নামের একটা ভেরিয়েবল ডিক্লেয়ার করা আছে। তুই যদি সেটা বাইরে থেকে কল করতে চাস, তখনও ইরোর খেয়ে যাবি।

javascript
  function add(a, b) {
    const total = a + b;
    console.log(a, b);
    return total;
  }

  // trying to access function parameter from outside
  console.log(a,b);
  add(5, 7);

Output: Uncaught ReferenceError: a is not defined

Global Scope

তুই যদি ফাংশন কোডের বাইরে বা যেকোনো ব্লকের (যেমন, { }-এর বাইরে) বাইরে ডিক্লেয়ার করিস, তখন সেটা Global Scope-এ থাকে। এই ভেরিয়েবল বা ফাংশনকে পুরো কোডে যেকোনো জায়গায় এক্সেস করা যায়।

javascript
let name = "Alex"; // Global Scope

function sayHello() {
  console.log("Hello, " + name);
}

sayHello();

Output: (Hello, Alex);

এখানে name গ্লোবাল স্কোপে ডিক্লেয়ার করা হয়েছে, তাই sayHello() ফাংশন থেকে সহজেই এক্সেস করা যাচ্ছে।

Lexical Scope

Lexical Scope হলো স্কোপিংয়ের একটা প্রিন্সিপাল, যেখানে ভেরিয়েবলের এক্সেসিবিলিটি নির্ভর করে কোডের লেখার ধরন এবং অবস্থানের ওপর। ভেতরের স্কোপের কোড বাইরের স্কোপের ভেরিয়েবল এক্সেস করতে পারে, কিন্তু বাইরের স্কোপ থেকে ভেতরের স্কোপের ভেরিয়েবল এক্সেস করা যায় না।

javascript
  function outerFunction() {
    let outerVar = "I'm from outer function";

    function innerFunction() {
      console.log(outerVar);
    }

    innerFunction();
  }

  outerFunction();

Output: I'm from outer function

এখানে innerFunction হচ্ছে outerFunction-এর ভেতরে ডিক্লেয়ার করা একটা ফাংশন, যেটা outerVar-কে এক্সেস করতে পারছে। কারণ, outerVar তার ওপরের স্কোপে আছে।

Scope চেইন

যখন তুই একটা ভেরিয়েবলকে এক্সেস করতে চাইবি, তখন জাভাস্ক্রিপ্ট স্কোপ চেইনের মাধ্যমে খুঁজে দেখে, ওই ভেরিয়েবলটা কোথায় আছে। ভেতরের স্কোপে খুঁজে না পেলে বাইরের স্কোপে খোঁজে, তারপরও না পেলে গ্লোবাল স্কোপে খোঁজে।

javascript
let name = "John";

function first() {
  let name = "Alex";

  function second() {
    console.log(name);
  }

  second();
}

first();

Output: Alex;

এখানে second ফাংশনের মধ্যে name খুঁজতে গেলে প্রথমে তার ওপরের স্কোপ first-এ খুঁজবে, সেখানে পেয়ে যাওয়ায় গ্লোবাল স্কোপে যাবে না।

সংক্ষেপে

Global Scope – পুরো কোডের যেকোনো জায়গা থেকে এক্সেস করা যায়।

Local Scope – নির্দিষ্ট ফাংশন বা ব্লকের মধ্যে সীমাবদ্ধ।

Lexical Scope – ভেতরের স্কোপ থেকে বাইরের স্কোপের ভেরিয়েবল এক্সেস করতে পারবি। কিন্তু বাইরের স্কোপ থেকে ভিতরের স্কোপের ভেরিয়েবল এক্সেস করতে পারবি না।

স্কোপের এই কনসেপ্ট কিছুটা অ্যাডভান্স কনসেপ্ট, তাই এখন না বুঝলেও টেনশন করিস না।

Practice:

  1. একটা গ্লোবাল ভেরিয়েবল taxRate ডিক্লেয়ার কর, যার মান হবে 15 । এইখানে taxRate-এর মান বলতে ট্যাক্স রেটের পার্সেন্টেজ বুঝায়। এরপর একটা ফাংশন লিখ, যেটা একজন মানুষের ইনকামকে ইনপুট হিসেবে নিবে। তারপর ফাংশনের ভিতরে taxRate-এর পার্সেন্টেজ ইউজ করে ট্যাক্সের পরিমাণ কত হবে, সেটা রিটার্ন করবে।
  2. একটা ফাংশন বানা, যেটার মধ্যে let দিয়ে insideSecret নামে ভেরিয়েবল ডিক্লেয়ার কর। এইটার মান হবে "internal secret hiding place" । ফাংশনের বাইরে থেকে insideSecret এক্সেস করতে চেষ্টা কর।
  3. if ব্লকের ভিতরে let দিয়ে temperature ভেরিয়েবল ডিক্লেয়ার কর এবং সেই ব্লকের বাইরে থেকে এক্সেস করার চেষ্টা কর।
  4. একটা ফাংশন বানা, যার নাম হবে schoolDetails। এই ফাংশনের ভেতরে schoolName নামে একটা ভেরিয়েবল ডিক্লেয়ার কর। এরপর schoolDetails ফাংশনের ভেতরে আরেকটা nested ফাংশন বানা, যার নাম হবে displaySchoolName। এই nested ফাংশন outer ফাংশনের schoolName ভেরিয়েবল এক্সেস করে সেটা console এ প্রিন্ট করবে। ফাংশনগুলো কল করার সময় যেন আউটপুটে স্কুলের নাম দেখা যায়। এখন বাহির থেকে schoolDetails ফাংশনকে কল কর।

Released under the MIT License.