সরাসরি পর্বে যান
পর্ব ২৬ · ৫৯-এর মধ্যে · মডিউল ৪

class, inheritance আর private field

JavaScript Classes — Constructors, Fields, #private, Static, extends & super, Composition

Taught in Bangla, this lesson covers JavaScript classes: what class syntax builds on top of prototypes, class fields and truly private #fields, getters, setters and static members, inheritance with extends and super, polymorphism and extending built-ins, practical traps like lost this and JSON serialisation, and when to prefer composition or skip classes entirely.

এই পর্বে যা শিখবেন

  • Class, constructor আর instance কী — আর class ভেতরে prototype দিয়ে কীভাবে কাজ করে
  • Class field, #private field ও method, getter/setter, static আর static block দিয়ে একটি নিরাপদ BankAccount বানানো
  • extends আর super — new চলার সময় কোন ধাপে কী হয়, method override আর lookup-এর ক্রম
  • Polymorphism, instanceof, Array ও Error extend করা, arrow-function field আর JSON-এর ফাঁদ
  • Composition, mixin আর “কখন class নয়” — আর AI-এর লেখা class-কোড যাচাই

১ · Class কী — একই ছাঁচে অনেক object

পর্ব ১৪-এ আপনি object literal দিয়ে একজন গ্রাহকের ছবি এঁকেছিলেন। এবার ধরুন আপনি একটি মোবাইল ব্যাংকিং অ্যাপ বানাচ্ছেন। প্রতিটি অ্যাকাউন্টের একজন মালিক আর একটি ব্যালেন্স আছে, আর প্রতিটি অ্যাকাউন্টে একই কাজ চলে: টাকা জমা, টাকা তোলা, ব্যালেন্স দেখা। হাজারটি অ্যাকাউন্টের জন্য হাজারবার একই object literal লিখবেন না। দরকার একটি ছাঁচ — যেখানে একবার বলা থাকবে “একটি অ্যাকাউন্টের কী কী থাকে আর সে কী কী পারে”, তারপর সেই ছাঁচ থেকে যত খুশি অ্যাকাউন্ট বানানো যাবে।

এই ছাঁচের নাম class. Class থেকে তৈরি প্রতিটি object-কে বলে সেই class-এর instance. আর instance তৈরির মুহূর্তে যে বিশেষ function চলে, নতুন object-এর প্রথম মানগুলো বসায়, তার নাম constructor:

class BankAccount {
  constructor(owner, balance) {   // new চলার সময় একবার চলে
    this.owner = owner;           // this = এইমাত্র তৈরি নতুন object
    this.balance = balance;
  }
  deposit(amount) {               // method — সব অ্যাকাউন্টের জন্য একটাই
    this.balance += amount;
  }
}

const rahim = new BankAccount("রহিম", 500);
const karim = new BankAccount("করিম", 2000);
rahim.deposit(100);
rahim.balance;                    // 600
karim.balance;                    // 2000 — আলাদা object, আলাদা ডেটা

class BankAccount { … } একটি class declaration. নাম বড় হাতের অক্ষরে শুরু করা প্রচলিত নিয়ম। new BankAccount("রহিম", 500) লিখলে new চারটি কাজ করে: একটি খালি object তৈরি হয়, তার prototype হয় BankAccount.prototype, constructor চলে যেখানে this সেই নতুন object, আর শেষে object-টি ফেরত আসে। rahim আর karim দুটি আলাদা instance — ডেটা আলাদা, কিন্তু deposit method দুজনের একই। Method লেখার ধরনটি পর্ব ১৪-এর method shorthand-এর মতো, শুধু মাঝে কমা নেই।

গার্মেন্টস কারখানার প্যাটার্ন-কাটারের কথা ভাবুন। একটি শার্টের কাগজের প্যাটার্ন (class) একবার বানানো হয় — কলারের মাপ, হাতার দৈর্ঘ্য, বোতামের জায়গা। তারপর সেই প্যাটার্ন থেকে হাজারটি শার্ট (instance) কাটা হয়। প্রতিটি শার্টের কাপড়ের রং আর সাইজ আলাদা হতে পারে (ডেটা), কিন্তু সেলাইয়ের নিয়ম একই (method). আর কাটিং টেবিলে প্রতিটি শার্টের প্রথম মাপ বসানোর কাজটিই constructor.

Class একটি function — কিন্তু বিশেষ ধরনের

একটি চমক: typeof BankAccount দেয় "function". JavaScript-এ class আসলে একটি constructor function, যেটি পর্ব ২৫-এ দেখা function BankAccount(owner) { this.owner = owner; }-এর মতোই কাজ করে। তবে পার্থক্যও আছে, আর প্রথম পার্থক্যটি একটি পুরোনো বাগ বন্ধ করে। পুরোনো constructor function-কে ভুল করে new ছাড়া ডাকলে this হতো global object, আর owner চুপচাপ global variable হয়ে যেত। Class-কে new ছাড়া ডাকলে সাথে সাথে error:

TypeError: Class constructor BankAccount cannot be invoked without 'new'

(এই বার্তাটি Chrome, Edge আর Node.js-এর; Firefox একই কথা অন্যভাবে বলে।) অর্থাৎ class “ভুলভাবে ব্যবহার করা কঠিন” — এটিই এর মূল সুবিধা।

C/C++/Python/Java জানলে

লেখার ধরন Java-র মতো দেখালেও ভেতরটা আলাদা: Java বা C++-এ class একটি স্থির type, compile-এর সময় object-এর field-তালিকা ঠিক হয়ে যায়; JavaScript-এ class চলার সময় তৈরি একটি function আর তার prototype object, পরে যেকোনো instance-এ নতুন property যোগ করা যায় (Java-র class ও object, C++-এর class). Python-এর self-এর জায়গায় এখানে this, আর সেটি parameter-এ লিখতে হয় না (Python-এর class). Java-র private keyword-এর কাজ করে #, আর inheritance শুধু একটি parent থেকে — interface নেই, তার জায়গায় mixin (Java-র inheritance). এগুলো না জানলে বাক্সটি বাদ দিন।

২ · Class আসলে কী বানায় — prototype-এর ওপর পরিষ্কার লেখা

পর্ব ২৫-এ দেখেছেন, ES2015-এর আগে “class” বানানো হতো দুই ধাপে: একটি constructor function, আর তার prototype-এ হাতে হাতে method বসানো। class একই জিনিস এক জায়গায়, পরিষ্কারভাবে লেখার উপায় — এ ধরনের সুবিধাকে বলে syntactic sugar. পাশাপাশি দেখুন:

// ES5 — পুরোনো ধরন
function OldAccount(owner) {
  this.owner = owner;
}
OldAccount.prototype.deposit = function (amount) { /* … */ };

// ES2015 — class
class BankAccount {
  constructor(owner) { this.owner = owner; }
  deposit(amount) { /* … */ }
}

Object.getOwnPropertyNames(new BankAccount("রহিম"));   // [ 'owner' ]
Object.getOwnPropertyNames(BankAccount.prototype);    // [ 'constructor', 'deposit' ]

Own property হিসেবে instance-এর ভেতরে আছে শুধু owner — constructor-এ this-এ যা বসানো হয়েছে। deposit instance-এ নেই; সেটি আছে BankAccount.prototype-এ, আর প্রতিটি instance prototype chain ধরে সেখান থেকে পায়। তাই দশ লাখ অ্যাকাউন্ট থাকলেও deposit function মেমরিতে একটাই। constructor property-টি prototype থেকে class-এর দিকে ফেরত link — rahim.constructor === BankAccount.

তবে “শুধু sugar” কথাটি পুরো সত্য নয়। পুরোনো ধরনের সাথে class-এর কয়েকটি গুরুত্বপূর্ণ পার্থক্য আছে:

বিষয়পুরোনো constructor functionclass
new ছাড়া ডাকাচলে — চুপচাপ ভুল (global-এ লেখে)TypeError
Hoistingfunction declaration — ঘোষণার আগেই ব্যবহার করা যায়TDZ — ঘোষণার আগে ছুঁলে ReferenceError
Strict modeনিজে "use strict" লিখতে হয়class-এর body সবসময় strict
Method কি for…in-এ দেখা যায়?হ্যাঁ — হাতে বসানো method enumerableনা — class method non-enumerable
Private field, static blockনেই (closure বা WeakMap দিয়ে কৌশল)ভাষার ভেতরেই (ES2022)
Array, Error extend করাঠিকভাবে সম্ভব নয়সম্ভব — extends Array

Class hoist হয় না, আর class-এর ভেতরে strict mode

পর্ব ০৭-এ দেখেছেন, function declaration-কে ঘোষণার আগেই ডাকা যায় (hoisting), কিন্তু let/const-এর একটি TDZ থাকে। Class আচরণ করে let-এর মতো:

const a = new Wallet();   // ReferenceError: Cannot access 'Wallet' before initialization
class Wallet {}

class Shop {
  open() {
    total = 0;            // ReferenceError: total is not defined (strict mode!)
  }
}

প্রথম ভুলটি ঘোষণার আগে class ব্যবহার — নাম আছে, কিন্তু TDZ-এ। এই নকশার কারণ: class B extends A-এ A একটি expression, যা ঘোষণার লাইনে এসে তবেই হিসাব হয়; পুরো class hoist করলে তখনো A তৈরি নাও থাকতে পারে। দ্বিতীয় ভুলটি strict mode-এর উপহার: সাধারণ script-এ total = 0 চুপচাপ একটি global variable বানিয়ে ফেলত (পর্ব ০৭-এর implicit global); class-এর ভেতরে সেটি সাথে সাথে ReferenceError. দুটি বার্তাই Chrome/Node.js-এর।

Class expression — class-ও একটি মান

Function-এর মতো class-ও একটি মান — variable-এ রাখা যায়, function-এ পাঠানো যায়, function থেকে ফেরত দেওয়া যায়: const Wallet = class { … };. একে বলে class expression. প্রতিদিন খুব বেশি লাগে না, কিন্তু অংশ ৮-এর mixin পুরোপুরি এর ওপর দাঁড়িয়ে — একটি function, যা একটি class নেয় আর নতুন class ফেরত দেয়।

৩ · Class field আর #private — ডেটা সত্যিই গোপন রাখা

আগের উদাহরণে একটি গুরুতর সমস্যা আছে: যে কেউ rahim.balance = 1000000 লিখে দিতে পারে। কোনো যাচাই নেই, কোনো লেনদেনের হিসাব নেই। বাস্তব ব্যাংকে ব্যালেন্স বদলায় শুধু জমা বা তোলার নিয়ম মেনে। এই নীতির নাম encapsulation — ডেটা ভেতরে লুকিয়ে রাখা, আর বাইরের জগৎকে শুধু নিয়ন্ত্রিত কয়েকটি দরজা (method) দেওয়া।

Public class field — constructor-এর বাইরে ঘর ঘোষণা

ES2022 থেকে class-এর body-তে সরাসরি field ঘোষণা করা যায়, constructor ছাড়াই: status = "active";. একে বলে class field. প্রতিটি instance তৈরির সময় এটি instance-এর নিজের property হিসেবে বসে — prototype-এ নয়। সুবিধা: class খুললেই এক নজরে দেখা যায় object-এর কী কী ঘর থাকবে, আর সব instance একই ক্রমে একই property পায় (পর্ব ০৫-এর hidden class খুশি থাকে)।

Field কখন বসে, সেই ক্রমটি গুরুত্বপূর্ণ: সাধারণ (parent নেই এমন) class-এ field-গুলো বসে constructor-এর body চলার ঠিক আগে, যে ক্রমে লেখা সেই ক্রমে। তাই constructor-এর ভেতরে field-এর মান পাওয়া যায়, আর এক field আগের field ব্যবহার করতে পারে। Child class-এ ক্রমটি একটু আলাদা — অংশ ৫-এর stepper-এ দেখবেন।

Private field আর private method — নামের শুরুতে #

নামের আগে # বসালে field হয়ে যায় private field: শুধু সেই class-এর body-র ভেতরের কোড এটি পড়তে বা লিখতে পারে। একইভাবে #validate() { … } একটি private method — বাইরের কেউ ডাকতে পারে না। দুটোই ES2022-এ যুক্ত হয়েছে, আর আধুনিক সব browser ও Node.js-এ চলে।

class BankAccount {
  #balance = 0;                   // private field — বাইরে অদৃশ্য
  #validate(amount) {             // private method
    if (!(amount > 0)) throw new RangeError("ভুল পরিমাণ");
  }
  deposit(amount) {
    this.#validate(amount);       // ভেতর থেকে — বৈধ
    this.#balance += amount;
  }
}
const acc = new BankAccount();
acc.balance;        // undefined — "balance" নামে সাধারণ property নেই
acc["#balance"];    // undefined — এটি শুধু "#balance" নামের একটি string key
// acc.#balance;    // SyntaxError — পুরো ফাইলই চলবে না

#balance কোনো সাধারণ property নয় — এটি string key দিয়ে খোঁজা হয় না, Object.keys, for…in, JSON.stringify, এমনকি Reflect.ownKeys-এও আসে না। acc["#balance"] সম্পূর্ণ আলাদা জিনিস: "#balance" নামের একটি সাধারণ string key খোঁজা, যা নেই। আর acc.#balance class-এর বাইরে লেখা ব্যাকরণগতভাবেই নিষিদ্ধ। ধাপে ধাপে দেখুন ঠিক কোথায় কী হয়:

class-এর ভেতরেবৈধ
class BankAccount {
  #balance = 0;
  deposit(a) { this.#balance += a; }   // ✅ একই class-এর body
  get balance() { return this.#balance; }
}
class-এর বাইরেparse-এর সময়েই SyntaxError
console.log("শুরু");          // এটিও ছাপা হবে না!
const acc = new BankAccount();
acc.#balance = 1000000;

SyntaxError: Private field '#balance' must be declared in an enclosing class
bracket আর অন্য নামশুধুই সাধারণ key
acc["#balance"]     → undefined  // string key, private নয়
acc._balance        → undefined
Object.keys(acc)    → []
JSON.stringify(acc) → "{}"
ভুল object পাঠালেচলার সময় TypeError
class BankAccount {
  #balance = 0;
  static peek(o) { return o.#balance; }   // ব্যাকরণে বৈধ
}
BankAccount.peek({ balance: 5 });

TypeError: Cannot read private member #balance from an object whose class did not declare it
#balance in objerror ছাড়া যাচাই (ES2022)
static isAccount(o) { return #balance in o; }

BankAccount.isAccount(new BankAccount())  → true
BankAccount.isAccount({ balance: 5 })     → false
  1. ভেতরে সব বৈধ। Class-এর body-র যেকোনো method, getter, static method — সবাই this.#balance পড়তে-লিখতে পারে। এমনকি একই class-এর অন্য instance-এর #balance-ও (other.#balance) ভেতর থেকে পড়া যায় — privacy class-ভিত্তিক, object-ভিত্তিক নয়।
  2. বাইরে — চলার আগেই থামে। Engine কোড চালানোর আগে পুরো ফাইল parse করে (পর্ব ০৫)। তখনই দেখে #balance নামটি কোনো ঘেরা class-এ ঘোষিত নয় — SyntaxError. ফলে ফাইলের প্রথম লাইনের console.log-ও চলে না। এটি runtime-এর অনুমতি-যাচাই নয়, ব্যাকরণের নিয়ম। (বার্তাটি Chrome/Node.js-এর।)
  3. Bracket দিয়ে ঢোকা যায় না। #balance-এর কোনো string নাম নেই, তাই acc["#balance"] একটি অন্য, অস্তিত্বহীন key খোঁজে। Object.keys, JSON, DevTools-এর বাইরে কোনো reflection — কোথাও private field দেখা যায় না। (DevTools আর Node.js-এর console.log ডিবাগের সুবিধার জন্য দেখাতে পারে, কিন্তু কোড দিয়ে পড়া যায় না।)
  4. ভুল object — চলার সময় TypeError. Class-এর ভেতরে o.#balance লেখা বৈধ, কারণ নামটি ঘোষিত। কিন্তু চলার সময় যদি o এই class দিয়ে তৈরি না হয়, তার ভেতরে সেই private ঘরটিই নেই — TypeError. Private field কখনো prototype chain ধরে খোঁজা হয় না।
  5. Error ছাড়া যাচাই — #balance in o. “এই object কি সত্যিই আমার class-এর তৈরি?” — এই প্রশ্নকে বলে brand check. instanceof ঠকানো যায় (Object.setPrototypeOf দিয়ে), কিন্তু private ঘর নকল করা যায় না। তাই #x in o সবচেয়ে নির্ভরযোগ্য পরিচয়-যাচাই।

_balance private নয়। বহু বছর ধরে JavaScript-এ প্রচলিত ছিল নামের আগে underscore দিয়ে বোঝানো “এটি ভেতরের জিনিস, ছোঁবেন না”। এটি শুধু একটি ভদ্র অনুরোধ — acc._balance = 999 দিব্যি চলে, Object.keys-এ দেখা যায়, JSON-এ চলে যায়। পুরোনো কোড আর লাইব্রেরিতে এখনো দেখবেন, আর AI-এর লেখা কোডেও প্রায়ই। নতুন কোডে সত্যিকারের গোপনীয়তা চাইলে #.

এবার পুরো BankAccount — private field, private method, getter, toJSON, static fromJSON আর brand check একসাথে চালিয়ে দেখুন:

class BankAccount {
  #balance = 0;                                    // private field
  constructor(owner, opening = 0) {
    this.owner = owner;
    if (opening > 0) this.deposit(opening);
  }
  #validate(amount) {                              // private method
    if (!(amount > 0)) throw new RangeError(`ভুল পরিমাণ: ${amount}`);
  }
  deposit(amount) { this.#validate(amount); this.#balance += amount; }
  withdraw(amount) {
    this.#validate(amount);
    if (amount > this.#balance) throw new RangeError("ব্যালেন্স যথেষ্ট নয়");
    this.#balance -= amount;
  }
  get balance() { return this.#balance; }
  toJSON() { return { owner: this.owner, balance: this.#balance }; }
  static fromJSON(text) { const d = JSON.parse(text); return new BankAccount(d.owner, d.balance); }
  static isAccount(obj) { return #balance in obj; }
}
const acc = new BankAccount("রহিম", 500); acc.deposit(200); acc.withdraw(100);
console.log(acc.owner, acc.balance, Object.getOwnPropertyNames(acc));
try { acc.withdraw(5000); } catch (e) { console.log(e.name, e.message); }
const back = BankAccount.fromJSON(JSON.stringify(acc));
console.log(JSON.stringify(back), BankAccount.isAccount(back), BankAccount.isAccount({ balance: 600 }));

প্রথম লাইন রহিম 600 [ 'owner' ] — ৫০০ + ২০০ − ১০০ = ৬০০, আর instance-এর নিজের নামধারী property শুধু owner; #balance সেই তালিকায় নেই। দ্বিতীয় লাইন RangeError ব্যালেন্স যথেষ্ট নয় — ৫০০০ তোলার চেষ্টা নিয়মে আটকাল, ব্যালেন্স অক্ষত। তৃতীয় লাইন {"owner":"রহিম","balance":600} true false — toJSON ঠিক করে দিল JSON-এ কী যাবে; fromJSON সেই লেখা থেকে নতুন, পূর্ণাঙ্গ instance বানাল (তাই brand check true), কিন্তু { balance: 600 } দেখতে যেমনই হোক, BankAccount নয়। balance getter থাকায় পড়া যায়, কিন্তু setter নেই — তাই বাইরে থেকে acc.balance = 1 লিখলে মান বদলায় না (strict mode-এ TypeError).

৪ · Getter, setter আর static — নিয়ন্ত্রিত দরজা আর class-এর নিজের জিনিস

Getter আর setter — property-র চেহারায় যাচাই

পর্ব ১৪-এ object literal-এ getter আর setter দেখেছেন। Class-এ এরা আরও কাজের, কারণ private field-এর সাথে জোড়া বাঁধে: ডেটা থাকে #price-এ, আর বাইরের জগৎ দেখে একটি সাধারণ দেখতে price property, যার পেছনে যাচাই চলে।

class Product {
  #price = 0;
  constructor(name, price) {
    this.name = name;
    this.price = price;            // setter চলে — শুরু থেকেই যাচাই
  }
  get price() { return this.#price; }
  set price(value) {
    if (!Number.isFinite(value) || value < 0) {
      throw new RangeError(`দাম ঠিক নয়: ${value}`);
    }
    this.#price = Math.round(value * 100) / 100;   // পয়সা পর্যন্ত
  }
  get priceWithVat() { return this.#price * 1.15; }   // শুধু পড়ার জন্য
}
const rice = new Product("মিনিকেট চাল", 80);
rice.price = 85.456;    // setter → 85.46
rice.price = -5;        // RangeError: দাম ঠিক নয়: -5

Constructor-এ this.price = price লিখে setter-কেই কাজে লাগানো হয়েছে — তাই ভুল দাম দিয়ে object তৈরিও সম্ভব নয়। Getter আর setter class-এর prototype-এ বসে, method-এর মতোই; instance-এ বসে শুধু #price-এর মান। priceWithVat-এর setter নেই, তাই এটি একটি হিসাব করা, শুধু-পড়ার property. একটি সতর্কতা: getter দেখতে property, তাই পড়ার সময় কেউ আশা করে না যে ভারী কাজ চলবে বা কিছু বদলাবে — getter-এ নেটওয়ার্ক ডাক বা ডেটা বদল রাখবেন না।

static — instance-এর নয়, class-এর নিজের

কিছু কাজ কোনো নির্দিষ্ট অ্যাকাউন্টের নয়, পুরো অ্যাকাউন্ট-ধারণার। যেমন JSON লেখা থেকে একটি অ্যাকাউন্ট বানানো, বা দুটি অ্যাকাউন্টের তুলনা। এগুলো static method — ডাকা হয় class-এর নাম দিয়ে (BankAccount.fromJSON(text)), কোনো instance দিয়ে নয়। আপনি আগেই অনেক static method ব্যবহার করেছেন: Math.max, Array.from, Object.keys, Number.isFinite.

বিষয়Instance method/fieldStatic method/field
কোথায় থাকেmethod: BankAccount.prototype-এ; field: প্রতিটি instance-এclass (function object) নিজের ভেতরে
কীভাবে ডাকেacc.deposit(100)BankAccount.fromJSON(text)
ভেতরে thisসেই instanceclass নিজে
ভুল জায়গায় ডাকলেBankAccount.deposit → undefinedacc.fromJSON → undefined, ডাকলে TypeError
সাধারণ ব্যবহারএকটি object-এর ডেটা নিয়ে কাজfactory (fromJSON), helper, ভাগের সেটিংস, গণনা

fromJSON-এর মতো method-কে বলে factory method: constructor-এর বদলে একটি নামওয়ালা দরজা দিয়ে object তৈরি। সুবিধা হলো নাম থেকেই উদ্দেশ্য বোঝা যায় — Money.fromPaisa(12550) আর Money.fromTaka(125.5) একই constructor-এ দুই অর্থের সংখ্যা পাঠানোর চেয়ে অনেক পরিষ্কার।

Static field, static #private আর static block (ES2022)

Static field (static vatRate = 0.15;) সব instance-এর ভাগের একটি মান রাখে, আর static #opened = 0; class-এর ভেতরের গোপন গণনা। কিন্তু কখনো class তৈরির মুহূর্তে একাধিক লাইনের প্রস্তুতি দরকার — একটি টেবিল ভরা, একটি সেটিং যাচাই করা। এর জন্য ES2022-এ এসেছে static block:

class VatTable {
  static rates = new Map();
  static {                                  // class তৈরির সময় একবার চলে
    for (const [item, rate] of [["চাল", 0], ["মোবাইল", 0.15]]) {
      this.rates.set(item, rate);           // এখানে this = VatTable
    }
  }
  static of(item) { return VatTable.rates.get(item) ?? 0.15; }
}
VatTable.of("চাল");       // 0
VatTable.of("ল্যাপটপ");   // 0.15 — তালিকায় নেই, তাই ডিফল্ট

Static block class-এর ঘোষণা চলার সময় একবারই চলে, ওপর থেকে নিচে static field-গুলোর সাথে ক্রম মেনে। এর আগে এমন প্রস্তুতি class-এর বাইরে লিখতে হতো, যেখানে class-এর private static ঘরে হাত দেওয়া যেত না। Static block class-এর ভেতরে, তাই #private static ঘরও ছুঁতে পারে, আর try/catch-সহ যেকোনো statement চালাতে পারে।

৫ · extends আর super — inheritance-এর পুরো যাত্রা

ব্যাংকে সাধারণ অ্যাকাউন্ট ছাড়াও সঞ্চয়ী অ্যাকাউন্ট আছে। এতে জমা, তোলা, ব্যালেন্স — সব একই; বাড়তি শুধু সুদ যোগ, আর মাসে তোলার একটি সীমা। সব কোড আবার লেখা অপচয়। Inheritance বলে: নতুন class পুরোনো class-এর সব কিছু উত্তরাধিকার হিসেবে পাক, আর শুধু পার্থক্যটুকু লিখুক। JavaScript-এ এর keyword extends:

class SavingsAccount extends BankAccount {   // BankAccount = parent (superclass)
  interestPaid = 0;                          // child-এর নিজের field
  constructor(owner, opening, rate) {
    super(owner, opening);                   // ১. আগে parent-এর constructor
    this.rate = rate;                        // ২. তারপর this ব্যবহার
  }
  addInterest() {                            // নতুন method
    const interest = Math.round(this.balance * this.rate);
    this.deposit(interest);                  // parent-এর method
    this.interestPaid += interest;
  }
  withdraw(amount) {                         // override — একই নামে নতুন নিয়ম
    if (amount > 20000) throw new RangeError("এক বারে ২০,০০০-এর বেশি নয়");
    super.withdraw(amount);                  // বাকি কাজ parent-এর withdraw
  }
}

SavingsAccount হলো subclass বা child, BankAccount হলো superclass বা parent. Child-এর constructor-এ super(...) ডেকে parent-এর constructor চালানো হয়। withdraw-কে child আবার লিখেছে — একে বলে method overriding. আর override-এর ভেতরে super.withdraw(amount) লিখে parent-এর মূল নিয়মটিও (ব্যালেন্স যাচাই, private #balance কমানো) কাজে লাগানো হয়েছে। খেয়াল করুন: child কখনো parent-এর #balance সরাসরি ছুঁতে পারে না — private field উত্তরাধিকারেও গোপন থাকে; child-কে public method বা getter দিয়েই যেতে হয়।

new SavingsAccount(...) — ঠিক কোন ক্রমে কী হয়

সাধারণ class-এ new প্রথমেই একটি খালি object বানিয়ে constructor-কে দেয়। Child class-এ তা হয় না — আর এই পার্থক্যই super()-এর নিয়মের মূল কারণ। ধাপে ধাপে দেখুন:

1class BankAccount {2  #balance = 0;3  constructor(owner, opening) {4    this.owner = owner; this.deposit(opening);5  } /* deposit, withdraw … */ }6class SavingsAccount extends BankAccount {7  interestPaid = 0;8  constructor(owner, opening, rate) {9    super(owner, opening);10    this.rate = rate;11  } }12const s = new SavingsAccount("করিম", 1000, 0.06);
তৈরি হতে থাকা object (this)
this
[[Prototype]]
#balance
owner
interestPaid
rate
  1. Child-এর constructor শুরু — কিন্তু object নেই। new SavingsAccount(...) child-এর constructor চালায়। কিন্তু derived class-এ this এখনো তৈরি হয়নি। এই মুহূর্তে this ছুঁলে ReferenceError.
  2. super(...) — parent object বানায়। নিয়ন্ত্রণ যায় BankAccount-এর কাছে। সবচেয়ে ওপরের (base) class-ই আসল object তৈরি করে — আর তার prototype বসায় SavingsAccount.prototype, কারণ engine মনে রাখে আসলে কোন class-কে new করা হয়েছিল (new.target).
  3. Parent-এর field বসে। BankAccount-এর constructor-এর body চলার ঠিক আগে তার class field — #balance = 0 — নতুন object-এ বসে। Child-এর field এখনো বসেনি।
  4. Parent-এর constructor-এর body. owner বসে, deposit(1000) চলে — #balance হয় ১০০০. Parent-এর কাজ শেষ, object ফেরত যায় super()-এর ডাকের জায়গায়।
  5. super() ফিরল — এবার child-এর field. এখন this ব্যবহারযোগ্য, আর ঠিক এই মুহূর্তে child-এর class field interestPaid = 0 বসে। তাই parent-এর constructor-এর ভেতর থেকে child-এর field দেখা যায় না — সেগুলো তখনো তৈরিই হয়নি।
  6. Child-এর constructor-এর বাকি অংশ। this.rate = rate চলে। Child নিজের প্রস্তুতি শেষ করল।
  7. সম্পূর্ণ object ফেরত। s পায় object-টি: নিজের ঘরে owner, interestPaid, rate আর গোপন #balance; prototype-এ SavingsAccount-এর method, তার পেছনে BankAccount-এর। ক্রমটি মনে রাখুন: parent field → parent body → child field → child body.

এই ক্রম থেকেই super()-এর নিয়ম আসে: child-এর constructor-এ this ছোঁয়ার আগে, বা constructor থেকে ফেরার আগে, super() ডাকতেই হবে — কারণ তার আগে object-ই নেই। ভুলে গেলে (Chrome/Node.js-এর বার্তা):

ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

Child-এ constructor একেবারেই না লিখলে কোনো সমস্যা নেই — engine নিজে constructor(...args) { super(...args); } বসিয়ে দেয়। সমস্যা হয় constructor লিখে super() ভুলে গেলে, বা তার আগে this ব্যবহার করলে।

Method lookup — override করা method কোনটি চলে?

s.withdraw(500) লিখলে দুটি withdraw আছে — child-এর আর parent-এর। কোনটি চলবে? উত্তর আসে পর্ব ২৫-এর prototype chain থেকে: engine নিচ থেকে ওপরে খোঁজে, আর প্রথম যেটি পায় সেটিই চালায়।

  1. s (instance)owner, interestPaid, rateশুধু ডেটা
  2. SavingsAccount.prototypewithdraw, addInterestchild-এর method
  3. BankAccount.prototypedeposit, withdraw, balanceparent-এর method
  4. Object.prototype → nulltoString, hasOwnPropertyসবার ভাগের
শুরুinstance-এর নিজের ঘর
s.withdraw(500)
→ s-এর নিজের property-তে "withdraw" নেই // method instance-এ থাকে না
s.withdrawপ্রথম পাওয়া = child-এরটি
→ SavingsAccount.prototype.withdraw পাওয়া গেল — এটিই চলে
// parent-এর withdraw "ঢাকা পড়ে গেল" (shadowing)
s.depositchild-এ নেই → parent-এ
s → SavingsAccount.prototype (নেই)
→ BankAccount.prototype.deposit পাওয়া গেল
super.withdraw(amount)খোঁজা শুরু এক ধাপ ওপর থেকে
child-এর withdraw-এর ভেতরে super.withdraw
→ খোঁজা শুরু BankAccount.prototype থেকে, s থেকে নয়
→ parent-এর withdraw চলে, this তখনো s
s.toString / s.flyশিকলের শেষ পর্যন্ত
s.toString → … → Object.prototype.toString
s.fly      → … → null → undefined
s.fly()    → TypeError: s.fly is not a function
  1. Instance-এ শুধু ডেটা। s-এর নিজের ঘরে আছে owner, interestPaid, rate (আর অদৃশ্য #balance). কোনো method নেই — তাই খোঁজা এক ধাপ ওপরে যায়।
  2. Child-এর prototype — প্রথম মিল জেতে। SavingsAccount.prototype-এ withdraw আছে, খোঁজা এখানেই শেষ। Parent-এর withdraw শিকলের আরও ওপরে, তাই তার কাছে যাওয়াই হয় না। Override আসলে এই সরল নিয়মের ফল — property shadowing.
  3. যা child-এ নেই, তা parent থেকে। s.deposit child-এর prototype-এ নেই, তাই engine যায় BankAccount.prototype-এ। এভাবেই child না লিখেও parent-এর সব method পায় — extends আসলে দুটি prototype-এর মধ্যে একটি link বসায়।
  4. super শুরু করে এক ধাপ ওপর থেকে। Method-টি যে prototype-এ লেখা (এখানে SavingsAccount.prototype), super.withdraw খোঁজে তার ঠিক ওপরের prototype থেকে। কিন্তু this বদলায় না — parent-এর withdraw-এর ভেতরেও this হলো s, তাই সে s-এর #balance কমায়।
  5. শিকলের শেষ। toString কেউ লেখেনি, তাই পাওয়া যায় Object.prototype-এ। আর যে নাম কোথাও নেই (fly) তা undefined; সেটিকে ডাকলে TypeError. Class লিখলেও property খোঁজার নিয়ম হুবহু পর্ব ২৫-এর।

এবার Product → DigitalProduct দিয়ে override, super.method(), field-এর ক্রম আর instanceof একসাথে চালিয়ে দেখুন:

class Product {
  category = "সাধারণ";                             // public field
  constructor(name, price) {
    this.name = name;
    this.price = price;
    this.seenByParent = this.category;              // parent constructor কী দেখে?
  }
  deliveryCharge() { return this.price >= 1000 ? 0 : 60; }
  describe() { return `${this.name}: ৳${this.price} + ডেলিভারি ৳${this.deliveryCharge()}`; }
}
class DigitalProduct extends Product {
  category = "ডিজিটাল";
  constructor(name, price, sizeMB) {
    super(name, price);                             // আগে parent, তারপর this
    this.sizeMB = sizeMB;
  }
  deliveryCharge() { return 0; }                    // override
  describe() { return `${super.describe()} (${this.sizeMB} MB)`; }
}
const cart = [new Product("রাইস কুকার", 2500), new Product("হেডফোন", 800), new DigitalProduct("ই-বুক", 150, 12)];
for (const item of cart) console.log(item.describe());
const ebook = cart[2];
console.log(ebook.seenByParent, ebook.category, ebook instanceof Product);
console.log(Object.getOwnPropertyNames(DigitalProduct.prototype), Object.hasOwn(ebook, "describe"));

প্রথম তিন লাইন রাইস কুকার: ৳2500 + ডেলিভারি ৳0, হেডফোন: ৳800 + ডেলিভারি ৳60, ই-বুক: ৳150 + ডেলিভারি ৳0 (12 MB). তৃতীয়টি খুব গুরুত্বপূর্ণ: super.describe() চলেছে parent-এর কোডে, কিন্তু তার ভেতরের this.deliveryCharge() ডেকেছে child-এর override (৳০, ৳৬০ নয়) — কারণ this এখনো ই-বুক object, আর lookup শুরু হয় তার থেকেই। চতুর্থ লাইন সাধারণ ডিজিটাল true — parent-এর constructor চলার সময় child-এর field বসেনি, তাই সে দেখেছে নিজের "সাধারণ"; পরে child-এর field সেটি বদলে দিয়েছে। শেষ লাইন [ 'constructor', 'deliveryCharge', 'describe' ] false — method prototype-এ, instance-এ নয়।

৬ · Polymorphism, instanceof আর built-in class extend করা

আগের উদাহরণের লুপটি আবার দেখুন: for (const item of cart) item.describe(). লুপ জানে না, জানার দরকারও নেই, কোনটি সাধারণ পণ্য আর কোনটি ডিজিটাল। প্রতিটি object নিজেই জানে নিজের deliveryCharge কীভাবে হিসাব করতে হয়। একই ডাক, ভিন্ন object-এ ভিন্ন আচরণ — একে বলে polymorphism. ক্ষেত্রফলের একটি ছোট উদাহরণ:

class Shape {
  area() { throw new Error(`${this.constructor.name} area() লেখেনি`); }
  toString() { return `${this.constructor.name} (ক্ষেত্রফল ${this.area().toFixed(2)})`; }
}
class Circle extends Shape {
  constructor(r) { super(); this.r = r; }
  area() { return Math.PI * this.r ** 2; }
}
class Rectangle extends Shape {
  constructor(w, h) { super(); this.w = w; this.h = h; }
  area() { return this.w * this.h; }
}
const shapes = [new Circle(1), new Rectangle(3, 4)];
shapes.map(String);   // [ 'Circle (ক্ষেত্রফল 3.14)', 'Rectangle (ক্ষেত্রফল 12.00)' ]
shapes.reduce((sum, s) => sum + s.area(), 0).toFixed(2);   // '15.14'

Shape একটি “abstract” parent-এর কাজ করে: সে area()-এর একটি চুক্তি ঘোষণা করে আর toString()-এর মতো ভাগের আচরণ দেয়। JavaScript-এ abstract keyword নেই, তাই প্রচলিত কৌশল হলো parent-এর method-এ error ছোড়া — কোনো child override করতে ভুলে গেলে ডাকার সময়ই ধরা পড়ে। this.constructor.name দেয় আসল class-এর নাম ("Circle"). নতুন আকার (Triangle) যোগ করতে শুধু একটি নতুন class লিখুন — যোগফলের কোড একটুও বদলাতে হয় না।

মূল ধারণা

JavaScript-এ polymorphism-এর জন্য inheritance বাধ্যতামূলক নয়। area() method আছে এমন যেকোনো object — class থেকে হোক বা object literal — ওই reduce-এ চলবে। একে বলে duck typing: “হাঁসের মতো হাঁটে, হাঁসের মতো ডাকে — তাহলে হাঁস”। Class-এর গাছ শুধু ভাগের কোড রাখার একটি উপায়, একমাত্র উপায় নয়।

instanceof — কী যাচাই করে, কী করে না

instanceof প্রশ্ন করে: “X.prototype কি এই object-এর prototype chain-এর কোথাও আছে?” তাই ebook instanceof DigitalProduct, ebook instanceof Product, ebook instanceof Object — তিনটিই true. দুটি সীমা মনে রাখুন। (১) এটি শুধু শিকল দেখে, তাই Object.setPrototypeOf দিয়ে ঠকানো যায়, আর JSON.parse থেকে আসা সাধারণ object কখনো instanceof BankAccount নয়, দেখতে যতই মিলুক। (২) iframe বা আলাদা window-এর Array আরেকটি Array constructor থেকে তৈরি, তাই সেখানে instanceof Array মিথ্যা হতে পারে — array যাচাইয়ে Array.isArray ভালো। নিজের class-এর নিশ্চিত পরিচয় চাইলে অংশ ৩-এর #field in obj.

Built-in class extend করা — TakaList extends Array, AppError extends Error

ES5-এ Array-এর একটি “child” বানানো ঠিকভাবে সম্ভব ছিল না — length-এর বিশেষ আচরণ পাওয়া যেত না। Class-এ সম্ভব, কারণ অংশ ৫-এ দেখেছেন: child-এ object বানায় parent (এখানে Array নিজে), তাই object-টি একটি আসল array.

class TakaList extends Array {
  total() { return this.reduce((s, x) => s + x, 0); }
}
const prices = TakaList.of(120, 80, 450);
prices.total();                         // 650
const doubled = prices.map((x) => x * 2);
doubled instanceof TakaList;            // true — map-ও TakaList ফেরত দেয়
doubled.total();                        // 1300

class AppError extends Error {
  constructor(message, options) {
    super(message, options);            // options-এ { cause } পাঠানো যায়
    this.name = "AppError";
  }
}
String(new AppError("পেমেন্ট ব্যর্থ"));   // 'AppError: পেমেন্ট ব্যর্থ'

map, filter-এর মতো method child class-এর instance-ই ফেরত দেয় (এই ব্যবস্থার নাম Symbol.species), তাই doubled.total() চলে। তবে সতর্কতা: built-in extend করা কোড engine-এর জন্য কিছুটা ধীর হতে পারে, আর পরে কোনো ভাষা-সংস্করণ একই নামের method যোগ করলে সংঘর্ষ হতে পারে। অনেক সময় একটি সাধারণ function — sumTaka(list) — বেশি সরল। কিন্তু Error extend করা খুব প্রচলিত আর উপকারী: catch-এ err instanceof AppError দিয়ে নিজের error আলাদা করা যায়। Custom error class, cause আর error-এর পুরো নকশা পর্ব ২৯-এ।

toString আর Symbol.toStringTag

একটি সাধারণ class instance-কে string-এ বদলালে আসে অকেজো "[object Object]". দুটি উপায়ে তা বদলানো যায়: একটি toString() method লেখা (Shape-এর মতো), যা template literal আর String(x)-এ ব্যবহার হয়; অথবা get [Symbol.toStringTag]() { return "Product"; } — তখন Object.prototype.toString.call(p) দেয় "[object Product]". দ্বিতীয়টি মূলত লাইব্রেরি আর ডিবাগিং-এর জন্য; Symbol দিয়ে ভাষার এমন hook-গুলো পর্ব ২৮-এ।

৭ · Class-এর তিনটি ব্যবহারিক ফাঁদ — হারানো this, JSON আর field-এর অর্থ

ফাঁদ ১: method-কে callback হিসেবে পাঠালে this হারায়

পর্ব ২৪-এ দেখেছেন, this ঠিক হয় ডাকার ধরনে। Class-এর method-কে আলাদা করে পাঠালে — button-এর click handler বা setTimeout-এ — ডটের বাঁ পাশে আর instance থাকে না — পরিচিত lost this বাগ। আর class-এর body strict, তাই this হয় undefined:

class Counter {
  count = 0;
  decrement() { this.count--; }            // prototype-এ, সবার ভাগের
  increment = () => { this.count++; };     // arrow field — প্রতিটি instance-এর নিজের
}
const k = new Counter();
const inc = k.increment, dec = k.decrement;
inc(); inc();          // ঠিক চলে — arrow-এর this চিরকাল k
k.count;               // 2
dec();                 // TypeError: Cannot read properties of undefined (reading 'count')
// সমাধানের অন্য উপায়: button.addEventListener("click", () => k.decrement());

increment একটি class field, যার মান একটি arrow function. Field বসে constructor চলার সময়, যখন this হলো নতুন instance; arrow নিজের this রাখে না, তাই সেই instance চিরকালের জন্য ধরা থাকে (পর্ব ২২-এর closure). React-এর পুরোনো class component-এ এই কৌশল খুব প্রচলিত ছিল। dec()-এর TypeError বার্তাটি Chrome/Node.js-এর।

কিন্তু এর দাম আছে, তাই “সব method arrow field বানাও” — AI-এর এই অভ্যাস ভালো নয়:

  • প্রতিটি instance-এ নতুন function. new Counter().increment === k.increment হলো false. দশ হাজার object মানে দশ হাজার function — prototype method-এ একটাই।
  • Override আর super ঠিকমতো চলে না। Arrow field prototype-এ নেই, তাই child-এ super.increment() লিখলে কিছু পাওয়া যায় না (undefined). আর child-এর prototype method parent-এর arrow field-কে override করতে পারে না — instance-এর নিজের property সবসময় আগে পাওয়া যায়।
  • টেস্টে prototype ধরে বদলানো যায় না, আর DevTools-এ প্রতিটি object-এ বাড়তি property দেখা যায়।

নিয়ম: method সাধারণভাবে prototype-এ লিখুন; শুধু যেগুলো সত্যিই callback হিসেবে বাইরে যায়, সেখানে arrow field বা ডাকার জায়গায় ছোট arrow (() => k.decrement())।

ফাঁদ ২: JSON-এ গেলে class হারায়

পর্ব ১৯-এ দেখেছেন, JSON.stringify শুধু নিজের, enumerable, সাধারণ property লেখে। Class instance-এর ক্ষেত্রে এর মানে: method যায় না (সেগুলো prototype-এ), private field যায় না (#balance কোনো property-ই নয়)। toJSON ছাড়া BankAccount হয়ে যেত শুধু {"owner":"রহিম"}. আর উল্টো দিকে JSON.parse সবসময় সাধারণ object দেয় — parsed.deposit হলো undefined, parsed instanceof BankAccount মিথ্যা।

তাই localStorage বা সার্ভারে class-এর ডেটা পাঠানোর প্রচলিত জোড়া হলো: toJSON() দিয়ে ঠিক করুন কী লেখা হবে, আর একটি static fromJSON() দিয়ে লেখা থেকে আবার আসল instance বানান — অংশ ৩-এর runnable কোডে ঠিক এটিই করেছেন। fromJSON-এ constructor-এর মধ্য দিয়ে গেলে আরেকটি লাভ: বাইরে থেকে আসা ডেটাও আপনার যাচাইয়ের নিয়ম পার হয়।

ফাঁদ ৩: class field মানে “define”, “assign” নয়

একটি সূক্ষ্ম কিন্তু বাস্তব পার্থক্য। Constructor-এ this.price = 100 হলো সাধারণ assignment — parent-এর prototype-এ price-এর setter থাকলে সেটি চলে। কিন্তু class field price = 100; property-টি সরাসরি instance-এ define করে (Object.defineProperty-এর মতো), তাই parent-এর setter চলে না, আর instance-এর নিজের property সেই getter/setter-কে ঢেকে দেয়। পুরোনো Babel বা TypeScript সেটিং-এ লেখা কোড নতুন সেটিং-এ গেলে এই কারণে আচরণ বদলে যেতে পারে — ভাবনার প্রশ্ন ৪-এ এর ইতিহাস।

৮ · Composition বনাম inheritance — আর কখন class নয়

Inheritance শক্তিশালী, কিন্তু একটি “is-a” (এটি একটি ওটা) সম্পর্ক চিরকালের জন্য বেঁধে দেয়। ধরুন আপনার দোকানে Product → PhysicalProduct → FragileProduct আছে। এবার এল “ডিজিটাল কিন্তু উপহার হিসেবে মোড়ানো যায়” — এটি কোন শাখায়? “ভঙ্গুর আর সাথে ঠান্ডা রাখতে হয়”? গাছ যত গভীর হয়, তত দেখা যায় বাস্তবের জিনিস একটি গাছে বসে না। ওপরের class-এ ছোট একটি বদল নিচের সব class-কে অপ্রত্যাশিতভাবে ভাঙতে পারে — একে বলে fragile base class সমস্যা।

বিকল্প হলো composition: “is-a”-এর বদলে “has-a” (এর একটি ওটা আছে)। পণ্য নিজে ডেলিভারির হিসাব জানবে না; তার কাছে একটি ডেলিভারি-নিয়ম object থাকবে, যেটি বদলানো যায়:

const courier = { charge: (p) => (p.price >= 1000 ? 0 : 60) };
const download = { charge: () => 0 };
const giftWrap = { extra: 30 };

class Product {
  constructor(name, price, delivery, addOns = []) {
    Object.assign(this, { name, price, delivery, addOns });   // has-a
  }
  total() {
    const extras = this.addOns.reduce((s, a) => s + a.extra, 0);
    return this.price + this.delivery.charge(this) + extras;
  }
}
new Product("ই-বুক", 150, download, [giftWrap]).total();   // 180
new Product("হেডফোন", 800, courier).total();               // 860

এখানে কোনো subclass নেই। ডিজিটাল-উপহার, ভঙ্গুর-ঠান্ডা — যেকোনো মিশ্রণ শুধু অংশগুলো জুড়ে তৈরি হয়। নতুন কুরিয়ার নিয়ম এলে Product-এর কোড ছুঁতে হয় না। ১৯৯৪ সালের বিখ্যাত বই Design Patterns-এর একটি মূল উপদেশই এটি: “class inheritance-এর চেয়ে object composition-কে প্রাধান্য দিন”।

Mixin — একাধিক জায়গা থেকে আচরণ ধার করা

JavaScript-এ একটি class শুধু একটি parent extend করতে পারে। কিন্তু কখনো একই আচরণ — যেমন “নিজেকে JSON-এ লিখতে পারা” — একেবারে আলাদা গাছের class-গুলোতে দরকার। সমাধান mixin. সবচেয়ে পরিষ্কার রূপ হলো এমন একটি function, যা একটি class নেয় আর তাকে extend করা নতুন class ফেরত দেয় (অংশ ২-এর class expression):

const Serializable = (Base) => class extends Base {
  serialize() { return JSON.stringify(this); }
};
const Timestamped = (Base) => class extends Base {
  stamp(at) { this.updatedAt = at; return this; }
};

class Customer { constructor(name) { this.name = name; } }
class SmartCustomer extends Serializable(Timestamped(Customer)) {}

const c = new SmartCustomer("নীলা").stamp("2026-10-19");
c.serialize();            // '{"name":"নীলা","updatedAt":"2026-10-19"}'
c instanceof Customer;    // true

Serializable(Timestamped(Customer)) চলার সময় একটি শিকল বানায়: SmartCustomer → Serializable-এর class → Timestamped-এর class → Customer. প্রতিটি mixin একটি ছোট, নির্দিষ্ট ক্ষমতা যোগ করে, আর যেকোনো class-এর সাথে জোড়া যায়। আরও সহজ (কিন্তু দুর্বল) একটি রূপও প্রচলিত: Object.assign(Invoice.prototype, { serialize() { … } }) — একটি object-এর method সরাসরি prototype-এ কপি করা। এতে super চলে না আর method-গুলো enumerable হয়ে যায়। Mixin-ও বেশি জমলে “কোন method কোথা থেকে এল” খুঁজে পাওয়া কঠিন — পরিমিত ব্যবহার করুন।

কখন class আদৌ লাগবে না

Java থেকে আসা অনেকে সবকিছু class দিয়ে শুরু করেন। JavaScript-এ এর দরকার নেই, আর প্রায়ই সরল কোডই ভালো:

✅ Class মানানসই

অনেকগুলো একই ধরনের object, যাদের ভেতরের অবস্থা (state) আছে আর নিয়ম মেনে বদলায় — অ্যাকাউন্ট, শপিং কার্ট, গেমের চরিত্র; গোপন ডেটা আর যাচাই দরকার (#private); framework বা API যা class চায় (Web Component, custom Error).

🤔 সাধারণ object + function যথেষ্ট

শুধু ডেটা বহন (সার্ভারের JSON, সেটিংস) — object literal; ইনপুট নিয়ে ফল দেওয়া কাজ (formatTaka(amount), calcVat(items)) — pure function (পর্ব ২৩); একটিমাত্র object — literal বা module (পর্ব ৪৫)।

একটি ভালো পরীক্ষা: যদি class-এ শুধু একটি constructor আর একটি method থাকে, বা সব method static হয়, তাহলে সেটি সম্ভবত একটি function বা একটি module হওয়া উচিত ছিল। React নিজেও ২০১৯ সালের দিকে hooks এনে class component থেকে function component-এ সরে গেছে — অনেকটা এই কারণেই।

পরীক্ষা ও ইন্টারভিউতে
  • JavaScript-এর class কি আসল class, নাকি syntactic sugar? — মূলত prototype-এর ওপর sugar (method prototype-এ থাকে), তবে private field, new-বাধ্যতা আর built-in extend-এর মতো নতুন ক্ষমতাও আছে।
  • Child constructor-এ super()-এর আগে this লিখলে কী হয়? — ReferenceError; derived class-এ object তৈরি করে parent, তাই super()-এর আগে this নেই।
  • #count আর _count-এর পার্থক্য? — # ভাষার নিশ্চিত private (বাইরে লিখলে SyntaxError); _ শুধু নামকরণের প্রথা, যে কেউ পড়তে-লিখতে পারে।
  • Static method কী, কোথায় ব্যবহার? — class দিয়ে ডাকা হয়, instance দিয়ে নয়; factory (fromJSON), helper বা ভাগের হিসাবে।
  • Class কি hoist হয়? — ঘোষণা hoist হয় কিন্তু TDZ-এ থাকে; ঘোষণার আগে ব্যবহার করলে ReferenceError, function declaration-এর মতো নয়।

৯ · AI-এর যুগে — AI-এর লেখা class-কোড যাচাই করা

AI টুল class খুব পছন্দ করে, কারণ তাদের শেখার ডেটায় Java আর C#-এর কোড প্রচুর। ফলে JavaScript-এ এমন class-কোড আসে যা দেখতে গোছানো, কিন্তু ভাষার শক্তি ব্যবহার করে না বা পুরোনো অভ্যাসে লেখা। সবচেয়ে সাধারণ ভুলগুলো:

AI-এর ভুলকেন সমস্যাকী চাইবেন
this._balance দিয়ে “private”যে কেউ পড়তে-লিখতে পারে, JSON-এ চলে যায়#balance আর getter
Java-র মতো গভীর গাছ (BaseEntity → Item → Product → …)fragile base class, বদলানো কঠিন১–২ স্তর, বাকিটা composition
Child constructor-এ super() ভুলে যাওয়া বা পরে ডাকাReferenceErrorconstructor-এর শুরুতেই super(...)
সব method arrow fieldপ্রতিটি instance-এ নতুন function, super চলে নাprototype method; callback-এ শুধু প্রয়োজনে arrow
প্রতিটি field-এর জন্য খালি getX()/setX()অকারণ লম্বা কোড, যাচাই নেইসাধারণ property, বা যাচাইসহ get/set
শুধু static method ভরা “Utils” classclass-এর কোনো কাজ নেইসাধারণ function বা module export
JSON.parse-এর ফলকে class instance ভাবাmethod নেই, instanceof মিথ্যাstatic fromJSON
Object.setPrototypeOf বা __proto__ দিয়ে inheritanceধীর, পড়তে কঠিনextends

AI-কে class লেখাতে চাইলে কাঠামো আর সীমা আগেই বলে দিন:

একটি মোবাইল ওয়ালেট অ্যাপের জন্য আধুনিক JavaScript (ES2022) class লিখে দাও: Wallet আর তার child MerchantWallet. নিয়ম: ১. ব্যালেন্স #balance private field-এ রাখবে; _underscore দিয়ে private বোঝাবে না। বাইরে শুধু balance getter থাকবে, setter নয়। ২. টাকা যাচাইয়ের কাজ #validate private method-এ; ভুল পরিমাণে RangeError ছুড়বে। ৩. inheritance এক স্তরের বেশি নয়। MerchantWallet-এর constructor-এ সবার আগে super() ডাকবে। ৪. MerchantWallet শুধু withdraw override করবে (প্রতি লেনদেনে ১.৫% চার্জ), আর মূল কাজের জন্য super.withdraw() ব্যবহার করবে। ৫. method prototype-এ লিখবে; arrow function field শুধু সেই method-এ, যেটি callback হিসেবে পাঠানো হবে — আর কেন, তা মন্তব্যে বলবে। ৬. toJSON আর static fromJSON লিখবে, যাতে JSON থেকে ফেরত আনা object আবার আসল Wallet হয়। ৭. শেষে console.log দিয়ে দেখাও: ব্যালেন্স, instanceof, আর JSON round-trip-এর পরে #balance in obj যাচাই — পুরো object log করবে না।

AI-এর লেখা class-কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১JavaScript-এ class এল ২০১৫ সালে, অথচ প্রায় দশ বছর আগেই একটি পূর্ণাঙ্গ class-ব্যবস্থার প্রস্তাব (ES4) তৈরি ছিল। সেটি কেন বাতিল হলো, আর ES2015-এর “maximally minimal classes” কেন ইচ্ছা করেই এত ছোট?

১৯৯৯ সালে ES3 প্রকাশের পরে TC39 পরের বড় সংস্করণ — ES4 — নিয়ে কাজ শুরু করে। প্রায় এক দশক ধরে ES4 হয়ে ওঠে একটি উচ্চাভিলাষী নতুন ভাষা: Java-র মতো class, interface, package, namespace, আর ঐচ্ছিক static type. এর বড় অংশ Adobe-এর Flash-এর ActionScript 3-এ বাস্তবে চলছিল, আর Mozilla-ও এর পক্ষে ছিল। কিন্তু Microsoft আর Yahoo-এর প্রতিনিধিরা, Douglas Crockford-সহ, আপত্তি তোলেন: এত বড় বদল ওয়েবের সাথে সামঞ্জস্য রাখবে কীভাবে, browser-গুলো বাস্তবায়ন করবে কীভাবে, আর ভাষাটি কি তার সরল চরিত্র হারাবে না? তাঁরা আলাদা করে একটি ছোট সংস্করণ — ES3.1 — এগিয়ে নেন।

২০০৮ সালের জুলাইয়ে অসলোর এক বৈঠকে বিরোধের মীমাংসা হয়: ES4 বাতিল, ES3.1 এগোবে (পরে এটিই ES5, ২০০৯), আর দীর্ঘমেয়াদি কাজের নাম হবে “Harmony”. Brendan Eich সেই ঘোষণায় স্পষ্ট বলেন, package আর namespace-এর মতো কিছু ধারণা চিরতরে বাদ, আর class আসবে আরও সরল রূপে — বিদ্যমান prototype মডেলের ওপরে।

Harmony-র যুগে class নিয়ে আবার বহু বছর তর্ক চলে: কেউ চাইতেন private, const property, trait — সব একসাথে; কেউ বলতেন class-ই লাগবে না। প্রতিবার প্রস্তাব বড় হলেই কোনো না কোনো বিষয়ে একমত হওয়া যেত না। শেষে ২০১২ সালের দিকে একটি কৌশল জেতে, যার নাম “maximally minimal classes”: শুধু সেটুকু রাখো যা নিয়ে সবাই একমত — constructor, prototype method, getter/setter, static method, extends আর super. Private, field, decorator — সব পরে আলাদা প্রস্তাবে। এই ছোট রূপটিই ES2015-এ আসে।

কৌশলটি কাজ করেছে। Class এসেছে, আর বাকি অংশগুলো ধীরে ধীরে, প্রতিটি আলাদা প্রমাণ আর তর্কের পরে যোগ হয়েছে — class field আর private ES2022-এ, static block ES2022-এ। Decorator বহু বছর প্রস্তাবের পর্যায়ে ঘুরেছে (২০২২ সালে Stage 3-এ পৌঁছায়), যদিও TypeScript-এ তার পুরোনো একটি রূপ অনেক আগে থেকেই চলছে।

মূল উপলব্ধি: একটি জীবন্ত, কোটি কোটি পাতায় চলা ভাষায় বড় নকশা একবারে চাপানো যায় না। ছোট, সবার সম্মত একটি ভিত্তি আগে দিন, বাকিটা ধাপে ধাপে — ES4-এর ব্যর্থতা আর ES2015-এর সাফল্য দুটোই এই শিক্ষা দেয়।

প্র ০২প্রায় সব ভাষায় private-এর জন্য একটি private keyword আছে। JavaScript কেন অদ্ভুত দেখতে # বেছে নিল — আর সেই সিদ্ধান্ত নিয়ে এত বিতর্ক কেন হয়েছিল?

# প্রথম দেখায় অনেকের কাছে কুৎসিত লেগেছিল, আর প্রস্তাবের GitHub পাতায় বছরের পর বছর ধরে শত শত মন্তব্যে এর বিরোধিতা হয়েছে। কিন্তু কমিটির যুক্তি ছিল ভাষার একটি মৌলিক বৈশিষ্ট্যে: JavaScript-এ টাইপ জানা থাকে না।

Java-তে other.balance লিখলে compiler জানে other কোন class-এর, তাই জানে balance private কি না, আর এই কোড থেকে পড়া বৈধ কি না। JavaScript-এ other যেকোনো কিছু হতে পারে। ধরুন private balance লেখা যেত, আর this.balance দিয়ে পড়া হতো। তাহলে other.balance দেখে engine-কে চলার সময় প্রতিবার ঠিক করতে হতো — এটি কি private ঘর, নাকি সাধারণ property? এতে প্রতিটি property পড়া ধীর হতো। আরও খারাপ, private আর public একই নামে থাকলে কোনটি বোঝানো হলো তা অস্পষ্ট হতো, আর বাইরের কোড "balance" in obj দিয়ে জেনে ফেলত যে একটি গোপন ঘর আছে — গোপনীয়তা ফাঁস।

আলাদা চিহ্ন এই সব সমস্যা মেটায়: #balance আর balance দুটি সম্পূর্ণ আলাদা নাম, parse-এর সময়েই বোঝা যায় কোনটি private, আর ভুল জায়গায় লিখলে SyntaxError. কমিটি “hard private” চেয়েছিল — বাইরের কোনো reflection-এ (Object.keys, Proxy, Object.getOwnPropertyNames) যেন private-এর অস্তিত্বও টের পাওয়া না যায়, যাতে লাইব্রেরি লেখকরা ভেতরের নাম বদলালে কারও কোড না ভাঙে। Kevin Gibbons-সহ প্রস্তাবের লেখকেরা একটি FAQ-তে এসব কারণ বিস্তারিত লিখেছেন।

এর সাথে এসেছে brand check-এর ধারণা: private ঘর আছে কি না দেখে নিশ্চিত হওয়া যায় যে object-টি সত্যিই এই class-এর constructor দিয়ে তৈরি। প্রথমে এটি করতে try { o.#x } catch লিখতে হতো, তাই ES2022-এ আলাদা প্রস্তাব হিসেবে #x in o যোগ হয়। Private field আর method স্তর ৪-এ পৌঁছায় ২০২১ সালে, আর ES2022-এর অংশ হয়।

মূল উপলব্ধি: ভাষার একটি ছোট চিহ্নের পেছনে থাকে তার মৌলিক নকশা। টাইপ ছাড়া একটি ভাষায় সত্যিকারের গোপনীয়তা চাইলে নামটিকেই আলাদা করতে হয় — # সৌন্দর্যের নয়, প্রয়োজনের সিদ্ধান্ত।

প্র ০৩“Inheritance-এর চেয়ে composition ভালো” — এই কথাটি কোথা থেকে এল? Inheritance যদি এত সমস্যার হয়, তাহলে OOP-এর মূল স্তম্ভ হিসেবে এত দিন শেখানো হলো কেন?

১৯৯০-এর দশকের শুরুতে C++ আর Smalltalk-এর জগতে inheritance ছিল OOP-এর প্রধান আকর্ষণ। বিজ্ঞাপন ছিল “কোড reuse” — একবার লিখুন, subclass করে বারবার ব্যবহার করুন। বড় বড় প্রজেক্টে গভীর class গাছ তৈরি হতে থাকে।

১৯৯৪ সালে Erich Gamma, Richard Helm, Ralph Johnson আর John Vlissides — যাঁরা “Gang of Four” নামে পরিচিত — প্রকাশ করেন Design Patterns: Elements of Reusable Object-Oriented Software. বইয়ের শুরুর অধ্যায়ে দুটি নীতি আছে, যা পরে বহুবার উদ্ধৃত হয়েছে: “implementation নয়, interface-এর দিকে লিখুন” আর “class inheritance-এর চেয়ে object composition-কে প্রাধান্য দিন”। তাঁদের যুক্তি: inheritance সবচেয়ে শক্ত বাঁধন। Subclass parent-এর ভেতরের খুঁটিনাটির ওপর নির্ভর করে, তাই একে বলা হয় “white-box reuse” — parent বদলালে child ভাঙে। আর সম্পর্কটি compile-এর সময় স্থির, চলার সময় বদলানো যায় না। Composition হলো “black-box reuse”: অংশগুলো শুধু একে অপরের public interface জানে, আর চলার সময় একটি অংশ বদলে অন্যটি বসানো যায়। বইয়ের বহু pattern — Strategy, Decorator, Composite — আসলে composition-এর নকশা।

পরে Java-র জগতে Joshua Bloch তাঁর Effective Java-তে একই উপদেশ দেন, আর fragile base class সমস্যার বাস্তব উদাহরণ দেখান: একটি subclass parent-এর দুটি method override করে গণনা রাখল, কিন্তু parent ভেতরে একটি method থেকে অন্যটিকে ডাকত — ফলে গণনা দ্বিগুণ হলো। আর সমাধানটিও নির্ভর করে parent-এর এমন একটি ভেতরের খুঁটিনাটির ওপর, যা পরের সংস্করণে বদলে যেতে পারে। নতুন ভাষাগুলো এই শিক্ষা নিয়েছে: Go-তে class inheritance নেই, শুধু struct embedding আর interface; Rust-এ inheritance নেই, আছে trait.

তার মানে inheritance খারাপ নয়। সত্যিকারের “is-a” সম্পর্কে, অগভীর গাছে — Error থেকে AppError, Shape থেকে Circle, একটি framework-এর base class — এটি পরিষ্কার আর উপকারী। সমস্যা হয় যখন শুধু কোড ভাগ করতে inheritance ব্যবহার হয়, আর গাছ চার-পাঁচ স্তরে নামে।

মূল উপলব্ধি: Inheritance একটি স্থায়ী, শক্ত সম্পর্ক; composition একটি নমনীয় জোড়া। প্রথমে জিজ্ঞেস করুন “এটি কি সত্যিই একটি ওটা?” — উত্তর পুরোপুরি হ্যাঁ না হলে “এর একটি ওটা আছে” দিয়ে ভাবুন।

প্র ০৪Class field price = 100; আর constructor-এর this.price = 100 — দুটো কি একই? TypeScript-এর useDefineForClassFields নামের সেটিং কেন বহু প্রজেক্টে হঠাৎ বাগ এনেছিল?

Class field প্রস্তাব নিয়ে TC39-এ একটি দীর্ঘ তর্ক ছিল: field-টি instance-এ কীভাবে বসবে? দুটি বিকল্প — [[Set]] মানে সাধারণ assignment, this.price = 100-এর মতো; এতে parent-এর prototype-এ price-এর setter থাকলে সেটি চলে। আর [[Define]] মানে Object.defineProperty(this, "price", …)-এর মতো; এতে setter উপেক্ষিত হয়, আর instance-এ সরাসরি একটি নতুন নিজের property তৈরি হয়।

কমিটি শেষে [[Define]] বেছে নেয়। যুক্তি ছিল: field একটি ঘোষণা — “এই object-এর একটি নিজের property থাকবে” — কোনো কাজের ধাপ নয়। Parent-এর setter গোপনে চলে গেলে ঘোষণার অর্থ বদলে যায়, আর parent-এ পরে একটি setter যোগ হলে child-এর আচরণ নিঃশব্দে বদলে যেত। অনেক ডেভেলপার এর বিরোধিতা করেছিলেন, কারণ ততদিনে বহু কোড [[Set]] আচরণের ওপর দাঁড়িয়ে ছিল।

সমস্যার কারণ ইতিহাস। মানদণ্ডে আসার বহু বছর আগে থেকেই TypeScript আর Babel class field সমর্থন করত, আর তারা field-কে constructor-এর ভেতরে this.price = 100-এ রূপান্তর করত — অর্থাৎ [[Set]]. মানদণ্ড ভিন্ন পথে যাওয়ায় TypeScript 3.7 (২০১৯) একটি সেটিং আনে — useDefineForClassFields — যা চালু করলে মানদণ্ডের [[Define]] আচরণ পাওয়া যায়। পরে target ES2022 বা তার নতুন হলে এটি ডিফল্টে চালু হয়ে যায়। তখন কিছু প্রজেক্টে দেখা যায়, parent class-এর setter আর চলছে না, বা একটি decorator-এর বসানো accessor child-এর field ঢেকে দিচ্ছে। বিশেষত MobX-এর মতো লাইব্রেরি, যারা prototype-এ getter/setter বসিয়ে কাজ করত, তাদের ব্যবহারকারীদের কোড বদলাতে হয়েছিল।

ব্যবহারিক শিক্ষা: parent-এর setter চালাতে চাইলে child-এ field নয়, constructor-এ this.x = … লিখুন। আর TypeScript প্রজেক্টের target বদলানোর সময় এই সেটিংটির কথা মাথায় রাখুন (পর্ব ৪৭)।

মূল উপলব্ধি: দেখতে একই দুটি লাইনের অর্থ ভিন্ন হতে পারে — ঘোষণা আর কাজ। আর একটি জনপ্রিয় টুল মানদণ্ডের আগে কিছু চালু করলে, মানদণ্ড ভিন্ন হলে সেই পার্থক্যের দাম একদিন সবাইকে দিতে হয়।

প্র ০৫অনেকে বলেন “JavaScript-এ আসলে class নেই, আছে শুধু prototype”. এই দাবি কতটা সত্য — prototype-ভিত্তিক আর class-ভিত্তিক OOP-এর মধ্যে আসল পার্থক্য কী?

Class-ভিত্তিক ভাষায় (Simula, Smalltalk, C++, Java) দুই ধরনের জিনিস আছে: class — যা নকশা, আর object — যা নকশা থেকে তৈরি। নকশা আর বস্তু আলাদা জগতে থাকে; Java-তে চলার সময় একটি class-এ নতুন method যোগ করা যায় না। Prototype-ভিত্তিক ভাষায় (Self, ১৯৮০-এর দশক) শুধু এক ধরনের জিনিস: object. একটি object আরেকটি object-কে “prototype” হিসেবে দেখায়, আর যা নিজের কাছে নেই তা সেখানে খোঁজে — একে বলে delegation. পর্ব ১৪-এর ভাবনার প্রশ্নে দেখেছেন, Brendan Eich JavaScript-এর object মডেল নিয়েছিলেন Self থেকে।

এই অর্থে দাবিটি সত্য: JavaScript-এর class নতুন ধরনের জিনিস তৈরি করে না। Class একটি function object, method-গুলো একটি সাধারণ prototype object-এ, আর চলার সময় সেই prototype-এ method যোগ বা বদল করা যায় — সব instance সাথে সাথে বদল দেখে। extends শুধু দুটি prototype-এর মধ্যে link বসায়, এমনকি extends-এর ডান পাশে যেকোনো expression (একটি function-এর ফল, mixin) বসানো যায়, যা Java-তে কল্পনাও করা যায় না।

কিন্তু “শুধু sugar” বলাও আর পুরো সত্য নয়। ES2015-এর class এমন কিছু করে যা আগের prototype-কৌশলে সম্ভব ছিল না: derived class-এ object তৈরি করে base class (এজন্যই Array আর Error ঠিকভাবে extend করা যায়), super-এর জন্য method মনে রাখে সে কোন object-এ লেখা ([[HomeObject]]), class constructor new ছাড়া চলে না। আর ES2022-এর private field-এর কোনো প্রত্যক্ষ prototype-রূপ নেই — এটি অনেকটা WeakMap-এর মতো আচরণ করে, কিন্তু ভাষার ব্যাকরণে বাঁধা। Kyle Simpson-এর মতো কেউ কেউ যুক্তি দেন, class-এর চেহারা মানুষকে Java-র মতো ভাবতে উৎসাহ দেয় আর prototype-এর আসল delegation মডেল আড়াল করে; অন্যরা বলেন, একটি পরিচিত, নিরাপদ চেহারা পুরো ইকোসিস্টেমকে এক ভাষায় কথা বলতে দিয়েছে।

ব্যবহারিক উত্তর: class লিখুন, কিন্তু ভাবুন prototype দিয়ে। তাহলে method lookup, super, instanceof-এর সীমা আর হারানো this — সবকিছুর কারণ পরিষ্কার থাকবে।

মূল উপলব্ধি: JavaScript-এর class হলো prototype মডেলের ওপর একটি পরিচিত, নিরাপদ মুখোশ — সঙ্গে কয়েকটি সত্যিকারের নতুন ক্ষমতা। মুখোশটি ব্যবহার করুন, কিন্তু নিচের যন্ত্রটি কীভাবে চলে, তা ভুলবেন না।

অনুশীলন

এগুলোর বেশিরভাগে কোড লিখতে হবে না — আগে অনুমান করুন, তারপর console-এ চালিয়ে মিলিয়ে নিন।

  1. ক্রম অনুমান করুন: class A { name = "A"; constructor() { this.seen = this.name; } } আর class B extends A { name = "B"; }. এখন const b = new B(); — b.seen আর b.name কী? B-তে constructor না লিখেও কেন কোনো error হলো না?

    b.seen হলো "A", b.name হলো "B". ক্রম: A-এর field (name = "A") → A-এর constructor-এর body (seen = "A") → B-এর field (name = "B", পুরোনো মান ঢেকে দেয়)। B-তে constructor না থাকলে engine নিজে constructor(...args) { super(...args); } বসিয়ে দেয়, তাই super() ভুলে যাওয়ার প্রশ্নই ওঠে না।

  2. বাগ খুঁজুন: AI লিখেছে — class Loan extends Account { constructor(owner, amount) { this._amount = amount; super(owner); } pay = (x) => { this._amount -= x; } }. তিনটি সমস্যা খুঁজে বের করুন।

    (১) super(owner)-এর আগে this ব্যবহার — new Loan(...) চালালেই ReferenceError; super আগে ডাকতে হবে। (২) _amount private নয় — বাইরে থেকে loan._amount = 0 লিখে ঋণ শূন্য করা যায়; #amount আর একটি getter লাগবে। (৩) pay একটি arrow field — প্রতিটি loan-এ নতুন function, আর child-এ super.pay() চলবে না; callback হিসেবে না পাঠালে সাধারণ method লিখুন। বোনাস: pay-এ কোনো যাচাই নেই — ঋণাত্মক বা ঋণের চেয়ে বেশি পরিমাণ আটকানো উচিত।

  3. নকশা নিয়ে ভাবুন: একটি কুরিয়ার কোম্পানির অ্যাপে পার্সেল আছে — সাধারণ, ভঙ্গুর, ঠান্ডা রাখার, আর ক্যাশ-অন-ডেলিভারি। একটি পার্সেল একসাথে ভঙ্গুর আর ক্যাশ-অন-ডেলিভারি হতে পারে। Inheritance-এর গাছ বানালে কী সমস্যা হবে? Composition দিয়ে কীভাবে সাজাবেন?

    গাছে প্রতিটি মিশ্রণের জন্য আলাদা class লাগবে — FragileCodParcel, ColdFragileParcel, ColdCodParcel… — চারটি বৈশিষ্ট্যে মিশ্রণ দ্রুত বাড়তে থাকে (combinatorial explosion), আর একটি class একসাথে দুটি parent extend করতে পারে না। Composition-এ একটি Parcel class, যার কাছে বৈশিষ্ট্যগুলোর একটি তালিকা: new Parcel(weight, [fragile, cod(1200)]); প্রতিটি বৈশিষ্ট্য একটি ছোট object, যেটি নিজের চার্জ (extra) আর নিজের নির্দেশনা (label) জানে। মোট চার্জ = ভিত্তি চার্জ + সব বৈশিষ্ট্যের extra. নতুন বৈশিষ্ট্য এলে শুধু একটি নতুন ছোট object.

  4. ছোট class লিখুন: Temperature class লিখুন — ভেতরে #celsius; celsius getter/setter (−২৭৩.১৫-এর নিচে হলে RangeError); শুধু-পড়ার fahrenheit getter; আর একটি static fromFahrenheit(f). তারপর Temperature.fromFahrenheit(98.6).celsius ছেপে দেখুন।

    একটি সমাধান: class Temperature { #celsius = 0; constructor(c) { this.celsius = c; } get celsius() { return this.#celsius; } set celsius(v) { if (v < -273.15) throw new RangeError("পরম শূন্যের নিচে"); this.#celsius = v; } get fahrenheit() { return this.#celsius * 9 / 5 + 32; } static fromFahrenheit(f) { return new Temperature((f - 32) * 5 / 9); } }. fromFahrenheit(98.6).celsius দেয় 37. তবে অন্য মানে floating-point-এর কারণে 36.99999999999999-এর মতো ফল আসতে পারে (পর্ব ১০), তাই দেখানোর সময় toFixed(1) ব্যবহার করা নিরাপদ। Constructor-এ setter ব্যবহার করায় ভুল তাপমাত্রা দিয়ে object তৈরিই হয় না।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমJavaScript-এ new Child() চালালে (Child extends Parent, দুটিতেই class field আছে) কোন ক্রমে কাজ হয়, সাজান (▲▼ চেপে বা টেনে)।

  1. 🚪 Child-এর constructor শুরু — this তখনো তৈরি হয়নি
  2. 📞 super() ডাকা — Parent object তৈরি করে
  3. 🧱 Parent-এর class field বসে
  4. ⚙️ Parent-এর constructor-এর body চলে
  5. 🧩 Child-এর class field বসে
  6. ✅ Child-এর constructor-এর বাকি body চলে

একটি ফাইলে class A { #x = 1 } ঘোষণার পরে class-এর বাইরে console.log(new A().#x); লেখা আছে। JavaScript-এ কী হয়?

  • 1 ছাপা হয়
  • undefined ছাপা হয়
  • চলার আগেই SyntaxError, ফাইলের কোনো লাইন চলে না
  • চলার সময় TypeError

JavaScript-এ একটি child class-এর constructor-এ super() ডাকার আগে this.rate = 5 লিখলে কী হয়?

  • rate বসে যায়, পরে super() বাকি কাজ করে
  • ReferenceError — super()-এর আগে this ছোঁয়া যায় না
  • SyntaxError, ফাইল চলেই না
  • engine নিজেই আগে super() ডেকে নেয়, কোনো সমস্যা নেই

JavaScript-এ class Shop { open() {} } থেকে এক হাজারটি instance বানালে open method কোথায় থাকে?

  • প্রতিটি instance-এর ভেতরে আলাদা কপি
  • Shop class-এর নিজের ভেতরে, static হিসেবে
  • Object.prototype-এ
  • Shop.prototype-এ, একবার — সব instance ভাগ করে

JavaScript-এ const w = new Wallet(); class Wallet {} — এই ক্রমে লিখলে কী হয়?

  • ReferenceError — class ঘোষণার আগে ব্যবহারযোগ্য নয়
  • ঠিক চলে, কারণ class hoist হয়
  • w হয় undefined
  • TypeError — class-কে new দিয়ে ডাকা যায় না

একটি class-এ public field owner, private field #balance আর method deposit() আছে, কিন্তু toJSON নেই। এর একটি instance-এ JavaScript-এর JSON.stringify কী লেখে?

  • owner আর #balance দুটোই
  • owner, #balance আর deposit — সবকিছু
  • শুধু owner
  • TypeError, কারণ class instance JSON-এ বদলানো যায় না

JavaScript class-এ handle = () => { … } (arrow function field) লেখার একটি আসল অসুবিধা কোনটি?

  • this সবসময় undefined হয়ে যায়
  • প্রতিটি instance-এ নতুন function তৈরি হয়, আর super দিয়ে ডাকা যায় না
  • আধুনিক browser-এ চলে না
  • callback হিসেবে পাঠালে this হারায়

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখতে পাবেন।

মনে রাখুন

  1. Class = ছাঁচ, instance = ছাঁচ থেকে তৈরি object. class ভেতরে একটি function আর তার prototype: method থাকে ClassName.prototype-এ, ডেটা থাকে instance-এ। new ছাড়া ডাকা যায় না, hoist হয় না (TDZ), body সবসময় strict.
  2. #private field ও method সত্যিই গোপন — বাইরে লিখলে parse-এর সময়েই SyntaxError, JSON বা Object.keys-এ আসে না। #x in obj দিয়ে নিশ্চিত brand check. _x শুধু প্রথা।
  3. Getter/setter দিয়ে যাচাইসহ দরজা; static method/field class-এর নিজের (factory fromJSON); static block class তৈরির সময় একবার চলে।
  4. extends-এ object বানায় parent: ক্রম parent field → parent body → child field → child body. তাই child-এ this-এর আগে super(). Override-এ প্রথম পাওয়া method জেতে; super.method() খোঁজা শুরু করে এক ধাপ ওপর থেকে।
  5. Arrow field শুধু callback-এর জন্য; JSON-এর জন্য toJSON/fromJSON; গভীর গাছের বদলে composition আর mixin; আর যেখানে state নেই, সেখানে class নয় — সাধারণ object আর function.

পরের পর্বে: for…of কীভাবে array, string, Map — এমনকি আপনার নিজের class-কেও ঘুরতে পারে? পর্ব ২৭-এ iterator আর generator: iteration protocol, function* আর yield দিয়ে ধীরে ধীরে মান তৈরি, আর নিজের class-কে for…of-এ ঘোরার উপযোগী বানানো।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান