ট্র্যাক
/
JavaScript
JavaScript
/
সিলেবাস
/
প্রোটোটাইপ ও ক্লাস
প্

প্রোটোটাইপ ও ক্লাস মধ্যে JavaScript

32টি অনুশীলনী

প্রোটোটাইপ ও ক্লাস সম্পর্কে

JavaScript-এ অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিং (OOP)-এর সুবিধা রয়েছে। OOP-তে, আপনি "টেমপ্লেট" (ক্লাস) থেকে অবজেক্ট (ইনস্ট্যান্স) তৈরি করতে চান, যাতে সেগুলোতে নির্দিষ্ট ডেটা ও কার্যকারিতা থাকে। OOP-র প্রসঙ্গে ডেটা প্রপার্টিগুলোকে ফিল্ড বলা হয়, আর ফাংশন প্রপার্টিগুলোকে বলা হয় মেথড।

২০১৫ সালে ভাষার স্পেসিফিকেশনে ক্লাস যুক্ত হওয়ার আগে JavaScript-এ ক্লাস বলে কিছুই ছিল না, তবে প্রোটোটাইপ-ভিত্তিক ইনহেরিট্যান্স ব্যবহার করে অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিং করা যেত। আর আজকাল class কিওয়ার্ড থাকলেও, JavaScript এখনও একটি প্রোটোটাইপ-ভিত্তিক ভাষা।

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

প্রোটোটাইপ সিনট্যাক্স

কনস্ট্রাক্টর ফাংশন

JavaScript-এ, টেমপ্লেট (ক্লাস) তৈরি করা হয় একটি সাধারণ ফাংশনের মাধ্যমে। কোনো ফাংশনকে যখন এমন টেমপ্লেট হিসেবে ব্যবহার করার কথা, তখন তাকে কনস্ট্রাক্টর ফাংশন বলা হয়, এবং প্রচলিত নিয়ম হলো ফাংশনের নাম বড় হাতের অক্ষর দিয়ে শুরু হওয়া উচিত। কনস্ট্রাক্টর ফাংশন কল করার সময় new কিওয়ার্ড ব্যবহার করে টেমপ্লেট থেকে ইনস্ট্যান্স (অবজেক্ট) তৈরি করা হয়।

function Car() {
  // ...
}

const myCar = new Car();
const yourCar = new Car();

এটা লক্ষ করা গুরুত্বপূর্ণ যে JavaScript-এ, ইনস্ট্যান্স তৈরি হওয়ার পরেও ইনস্ট্যান্সগুলো আর কনস্ট্রাক্টর ফাংশনের মধ্যে পারস্পরিক সম্পর্ক থেকে যায়। প্রতিটি ইনস্ট্যান্স অবজেক্টে একটি লুকানো, অভ্যন্তরীণ প্রপার্টি থাকে, যাকে ভাষার স্পেসিফিকেশনে [[prototype]] বলা হয়। এটি কনস্ট্রাক্টর ফাংশনের prototype কী (key)-এর মানের একটি রেফারেন্স ধারণ করে। হ্যাঁ, আপনি ঠিকই পড়েছেন, একটি JavaScript ফাংশনে কী (key)/মান জোড়া থাকতে পারে, কারণ আড়ালে এটি নিজেও একটি অবজেক্ট।

২০১৫ সাল থেকে, Object.getPrototypeOf()-এর মাধ্যমে [[prototype]] অ্যাক্সেস করা যায়। তার আগে, অনেক এনভায়রনমেন্টে __proto__ কী (key) দিয়ে এটি অ্যাক্সেস করা যেত।

একটি অবজেক্টের প্রোটোটাইপ ([[prototype]])-এর সাথে কনস্ট্রাক্টর ফাংশনের prototype প্রপার্টিকে গুলিয়ে ফেলবেন না।

Note

সংক্ষেপে:

  • JavaScript-এ কনস্ট্রাক্টর হলো সাধারণ ফাংশন।
  • নতুন ইনস্ট্যান্স তৈরি করলে এমন একটি অবজেক্ট তৈরি হয় যার সাথে তার কনস্ট্রাক্টরের একটি সম্পর্ক থাকে, যাকে তার প্রোটোটাইপ বলা হয়।
  • ফাংশনও অবজেক্ট (কল করা যায় এমন অবজেক্ট), তাই তাদেরও প্রপার্টি থাকতে পারে।
  • কনস্ট্রাক্টর (ফাংশন)-এর prototype প্রপার্টিই ইনস্ট্যান্সের প্রোটোটাইপ হয়ে ওঠে।

ইনস্ট্যান্স ফিল্ড

অনেক সময় আপনি চান, তৈরি হওয়া সব অবজেক্টে (ইনস্ট্যান্সে) কিছু ফিল্ড থাকুক, আর অবজেক্ট তৈরি হওয়ার সময় সেগুলোর জন্য কিছু প্রাথমিক মান দেওয়া হোক। এটা করা যায় this কিওয়ার্ড-এর মাধ্যমে। কনস্ট্রাক্টর ফাংশনের ভেতরে, this সেই নতুন অবজেক্টকে বোঝায় যা new দিয়ে তৈরি হবে। new দিয়ে কল করলে this স্বয়ংক্রিয়ভাবে কনস্ট্রাক্টর ফাংশন থেকে রিটার্ন করা হয়।

তার মানে, কনস্ট্রাক্টর ফাংশনে this-এ যোগ করে আমরা নতুন ইনস্ট্যান্সে ফিল্ড যোগ করতে পারি।

function Car(color, weight) {
  this.color = color;
  this.weight = weight;
  this.engineRunning = false;
}

const myCar = new Car('red', '2mt');
myCar.color;
// => 'red'
myCar.engineRunning;
// => false

ইনস্ট্যান্স মেথড

মেথড যোগ করা হয় কনস্ট্রাক্টর ফাংশনের prototype প্রপার্টির মাধ্যমে। একটি মেথডের ভেতরে, this-এর মাধ্যমে ইনস্ট্যান্সের ফিল্ডগুলো অ্যাক্সেস করতে পারেন। এটি কাজ করে নিচের সাধারণ নিয়মের কারণে।

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

function Car() {
  this.engineRunning = false;
  // ...
}

Car.prototype.startEngine = function () {
  this.engineRunning = true;
};

Car.prototype.addGas = function (litre) {
  // ...
};

const myCar = new Car();
myCar.startEngine();
myCar.engineRunning;
// => true

প্রোটোটাইপ চেইন

উপরের উদাহরণে myCar একটি সাধারণ JavaScript অবজেক্ট, আর আমরা যদি এটি পরীক্ষা করি (যেমন ব্রাউজার কনসোলে), তাহলে myCar অবজেক্টের ভেতরে সরাসরি মান হিসেবে একটি ফাংশনসহ startEngine নামের কোনো প্রপার্টি পাব না। তাহলে উপরের কোডটি কীভাবে কাজ করছে?

এখানে রহস্যটির নাম প্রোটোটাইপ চেইন। কোনো অবজেক্টের যেকোনো প্রপার্টি (ফিল্ড বা মেথড) অ্যাক্সেস করার চেষ্টা করলে, JavaScript প্রথমে দেখে সংশ্লিষ্ট কী (key) অবজেক্টের ভেতরেই সরাসরি পাওয়া যায় কি না। না পাওয়া গেলে, এটি মূল অবজেক্টের [[prototype]] প্রপার্টি যে অবজেক্টকে নির্দেশ করে, তার মধ্যে কী (key)-টি খুঁজতে থাকে। আগে বলা হয়েছে, আমাদের উদাহরণে [[prototype]] কনস্ট্রাক্টর ফাংশনের prototype প্রপার্টিকে নির্দেশ করে। ওখানেই JavaScript startEngine ফাংশনটি পাবে, কারণ আমরা সেটি সেখানেই যোগ করেছি।

function Car() {
  // ...
}

Car.prototype.startEngine = function () {
  // ...
};

আর চেইন এখানেই শেষ হয় না। Car.prototype-এর [[prototype]] প্রপার্টি (myCar.[[prototype]].[[prototype]]) নির্দেশ করে Object.prototype-কে (Object কনস্ট্রাক্টর ফাংশনের prototype প্রপার্টি)। এতে সব JavaScript অবজেক্টের জন্য উপলব্ধ সাধারণ মেথড থাকে, যেমন toString()। Object-এর [[prototype]] সাধারণত null, তাই প্রোটোটাইপ চেইন ওখানেই শেষ হয়। সব মিলিয়ে, আপনি myCar.toString() কল করতে পারেন এবং সেই মেথডটি থাকবে, কারণ JavaScript পুরো প্রোটোটাইপ চেইন জুড়ে সেই মেথডটি খোঁজে। বিস্তারিত উদাহরণ পাবেন MDN নিবন্ধ "Inheritance and the prototype chain"-এ।

Caution

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

ডাইনামিক মেথড (সব বিদ্যমান ইনস্ট্যান্সে মেথড যোগ করা)

ইনস্ট্যান্স তৈরি হওয়ার পরেও JavaScript আপনাকে সব বিদ্যমান ইনস্ট্যান্সে মেথড যোগ করার সুযোগ দেয়।

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

function Car() {
  this.engineRunning = false;
}

const myCar = new Car();
// Calling myCar.startEngine() here would result in "TypeError:
// myCar.startEngine is not a function".

Car.prototype.startEngine = function () {
  this.engineRunning = true;
};

myCar.startEngine();
// This works, even though myCar was created before the method
// was added.

তত্ত্বগতভাবে, ডাইনামিক মেথড ব্যবহার করে Object বা Array-এর মতো বিল্ট-ইন অবজেক্টের প্রোটোটাইপ পরিবর্তন করে তাদের কার্যকারিতা বাড়ানোও যায়। একে মনকি প্যাচিং বলা হয়। এই পরিবর্তন পুরো অ্যাপ্লিকেশনকে প্রভাবিত করে, তাই অনভিপ্রেত পার্শ্বপ্রতিক্রিয়া এড়াতে এটি করা উচিত নয়। একমাত্র যুক্তিসঙ্গত ব্যবহার হলো পুরোনো এনভায়রনমেন্টে অনুপস্থিত কোনো মেথডের জন্য polyfill সরবরাহ করা।

ক্লাস সিনট্যাক্স

আজকাল JavaScript class কিওয়ার্ড দিয়ে ক্লাস ডিফাইন করা সমর্থন করে। এটি ২০১৫ সালে ভাষার স্পেসিফিকেশনে যুক্ত করা হয়েছিল। একদিকে, এটি সিনট্যাক্টিক সুগার দেয়, যা ক্লাস পড়া ও লেখা সহজ করে। নতুন সিনট্যাক্স C++ বা Java-এর মতো ভাষায় ক্লাস লেখার পদ্ধতির সাথে বেশি মিল। ওই ভাষাগুলো থেকে আসা ডেভেলপারদের খাপ খাওয়ানো সহজ হয়। অন্যদিকে, ক্লাস সিনট্যাক্স নতুন ভাষা ফিচারের পথ খুলে দেয়, যা প্রোটোটাইপ সিনট্যাক্সে পাওয়া যায় না।

ক্লাস ডিক্লারেশন

নতুন সিনট্যাক্সে, ক্লাস ডিফাইন করা হয় class কিওয়ার্ড দিয়ে, তারপর থাকে ক্লাসের নাম এবং দ্বিতীয় বন্ধনীর ভেতরে ক্লাস বডি। বডিতে থাকে কনস্ট্রাক্টর ফাংশনের সংজ্ঞা, অর্থাৎ constructor নামের একটি বিশেষ মেথড। এই ফাংশনটি প্রোটোটাইপ সিনট্যাক্সের কনস্ট্রাক্টর ফাংশনের মতোই কাজ করে। ক্লাস বডিতে থাকে সব মেথড ডেফিনিশনও। মেথডের সিনট্যাক্স অবজেক্টের ভেতরে মান হিসেবে ফাংশন যোগ করার যে সংক্ষিপ্ত নোটেশন আমরা দেখেছি, তার মতোই, দেখুন অবজেক্ট কনসেপ্ট।

class Car {
  constructor(color, weight) {
    this.color = color;
    this.weight = weight;
    this.engineRunning = false;
  }

  startEngine() {
    this.engineRunning = true;
  }

  addGas(litre) {
    // ...
  }
}

const myCar = new Car();
myCar.startEngine();
myCar.engineRunning;
// => true

ফাংশন ডিক্লারেশন ও ফাংশন এক্সপ্রেশনের মতোই, উপরে দেখানো ক্লাস ডিক্লারেশন-এর পাশাপাশি JavaScript ক্লাস এক্সপ্রেশনও সমর্থন করে।

মনে রাখবেন, আড়ালে JavaScript এখনও একটি প্রোটোটাইপ-ভিত্তিক ভাষা। উপরের "প্রোটোটাইপ সিনট্যাক্স" অংশে আমরা যে সব কার্যপ্রণালী জেনেছি, সেগুলো এখনও প্রযোজ্য।

প্রাইভেট ফিল্ড, গেটার ও সেটার

ডিফল্টভাবে, JavaScript-এ সব ইনস্ট্যান্স ফিল্ড পাবলিক। এগুলো সরাসরি অ্যাক্সেস করা ও অ্যাসাইন করা যায়।

ভাষার স্পেসিফিকেশনে প্রকৃত প্রাইভেট ফিল্ড যোগ করার কাজ এখনও চলছে, বিস্তারিত জানতে দেখুন প্রস্তাবনা নথি।

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

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

class Car {
  constructor() {
    this._mileage = 0;
  }

  get mileage() {
    return this._mileage;
  }

  set mileage(value) {
    throw new Error(`Mileage cannot be manipulated, ${value} is ignored.`);
    // Just an example, usually you would not provide a setter in this case.
  }
}

const myCar = new Car();
myCar.mileage;
// => 0
myCar.mileage = 100;
// => Error: Mileage cannot be manipulated, 100 is ignored.

ক্লাস ফিল্ড ও ক্লাস মেথড

OOP-তে, অনেক সময় আপনি এমন ইউটিলিটি ফিল্ড বা মেথড দিতে চান, যা নির্দিষ্ট কোনো ইনস্ট্যান্সের ওপর নির্ভর করে না। বরং, এগুলো ক্লাসের জন্যই ডিফাইন করা হয়। এটা করা যায় static কিওয়ার্ড দিয়ে।

class Car {
  static type = 'vehicle';

  static isType(targetType) {
    return targetType === 'vehicle';
  }
}

Car.type;
// => 'vehicle'

Car.isType('road sign');
// => false

ক্লাস-ভিত্তিক ইনহেরিট্যান্স

আগে আমরা প্রোটোটাইপ চেইন বরাবর যে ধরনের ইনহেরিট্যান্স দেখেছি, তা ছাড়াও JavaScript-এ ক্লাসের মধ্যে ইনহেরিট্যান্সও প্রকাশ করা যায়। এটি আলোচনা করা হয়েছে ইনহেরিট্যান্স কনসেপ্ট-এ।


  1. this-এর উদাহরণ - অবজেক্ট মেথড হিসেবে, MDN। https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#as_an_object_method (ডিসেম্বর ০৩, ২০২১ তারিখে রেফারেন্স করা) ↩

GitHub-এর মাধ্যমে সম্পাদনা করুন লিংকটি নতুন একটি উইন্ডো বা ট্যাবে খুলবে

প্রোটোটাইপ ও ক্লাস শিখুন