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

prototype আর prototype chain — JavaScript-এর উত্তরাধিকার

JavaScript Prototypes — [[Prototype]], the Prototype Chain, Object.create, Constructors & new

Taught in Bangla, this lesson explains prototypal inheritance: behaviour is borrowed, not copied, the confusing [[Prototype]] versus prototype naming, Object.create, how property lookup walks up the chain while writes do not, constructor functions and what new does, instanceof and isPrototypeOf, built-in chains and the danger of modifying them, and keeping chains stable for speed.

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

  • [[Prototype]] আর prototype property-র পার্থক্য পরিষ্কারভাবে, সাথে Object.getPrototypeOf, setPrototypeOf আর পুরোনো __proto__
  • Property খোঁজার পুরো নিয়ম, shadowing, আর কেন for…in inherited property দেখায় কিন্তু Object.keys দেখায় না
  • Object.create(proto) আর Object.create(null) — নিরাপদ dictionary object
  • Constructor function আর new-এর চারটি ধাপ, method ভাগাভাগি, constructor property, instanceof বনাম isPrototypeOf, আর দুই constructor-এর মধ্যে উত্তরাধিকার
  • Built-in chain (array, function, string), built-in prototype বদলানোর বিপদ, polyfill, আর prototype বদলালে engine কেন ধীর হয়

১ · মূল ধারণা — আচরণ কপি হয় না, ধার করা হয়

পর্ব ১৪-এ একটি অবাক করা জিনিস দেখেছিলেন: { name: "Riya" }-এর ভেতরে আপনি কোনো toString লেখেননি, তবুও cust.toString() চলে। কোথা থেকে এল? সেখানে উত্তরের শুরুটা দেখেছিলেন — একটি লুকানো শিকল। এই পর্বে সেই শিকলের পুরো গল্প।

Java, C++ বা Python-এর মতো বেশিরভাগ ভাষায় object তৈরি হয় একটি class নামের ছাঁচ থেকে। ছাঁচে লেখা থাকে কোন কোন method থাকবে, আর প্রতিটি object সেই ছাঁচের একটি “কপি”। JavaScript-এর মূল নকশা আলাদা। এখানে কোনো ছাঁচ লাগে না। প্রতিটি object-এর ভেতরে একটি লুকানো link থাকে আরেকটি সাধারণ object-এর দিকে — সেটিই তার prototype. কোনো property নিজের ভেতরে না পেলে object বলে, “আমার নেই, আমার prototype-কে জিজ্ঞেস করো।” Prototype-এরও না থাকলে সে আবার তার prototype-কে জিজ্ঞেস করে। এভাবে কাজ অন্যকে দিয়ে করিয়ে নেওয়াকে বলে delegation.

একটি বড় পরিবারের কথা ভাবুন। ছোট ভাই রাহাতের কাছে বাড়ির চাবি নেই। কেউ চাবি চাইলে রাহাত বলে, “আমার কাছে নেই, বড় আপুর কাছে দেখো।” আপুর কাছেও না থাকলে তিনি বলেন, “আব্বার কাছে আছে।” চাবি একটিই — আব্বার কাছে — কিন্তু পরিবারের সবাই চাবি “দিতে পারে”, কারণ সবাই জানে কাকে জিজ্ঞেস করতে হবে। প্রতিটি সন্তানের জন্য আলাদা চাবি বানাতে হয়নি। আবার রাহাত যদি নিজের জন্য একটি চাবি বানায়, তখন কেউ চাইলে সে নিজেরটিই দেবে — আব্বার চাবি তাতে বদলায় না। Prototype chain ঠিক এভাবে কাজ করে: method থাকে একবার, শিকলের ওপরে; সবাই ধার করে।

কেন এই নকশা কাজের — তিনটি সুবিধা

🧠 মেমরি বাঁচে

এক হাজার পণ্যের object-এ একই priceWithVat function এক হাজারবার না রেখে prototype-এ একবার রাখা যায়। সব পণ্য শিকল ধরে একটিই function পায়।

🔄 এক জায়গায় বদল, সবখানে প্রভাব

Prototype-এর method বদলালে আগে তৈরি সব object নতুন আচরণ পায়, কারণ তারা কপি রাখেনি — link রেখেছে। এটি শক্তি, আবার ঝুঁকিও (অংশ ৭)।

🏛️ class এরই ওপর দাঁড়িয়ে

ES2015-এর class keyword নতুন object মডেল আনেনি। class Product { … } লিখলে ভেতরে এই পর্বের constructor function আর prototype-ই তৈরি হয় — পর্ব ২৬-এ দেখবেন।

এই ধারণা এসেছে Self নামের একটি গবেষণা-ভাষা থেকে, যা David Ungar আর Randall Smith ১৯৮৭ সালে প্রথম প্রকাশ করেন — সেখানে class নেই, শুধু object আর তার “parent”. ১৯৯৫ সালে Netscape-এ Brendan Eich JavaScript-এর object মডেল নেন Self থেকে, কিন্তু Netscape চেয়েছিল ভাষাটি দেখতে Java-র মতো হোক — তাই এসেছে new আর constructor function. একই ভাষায় দুই দর্শনের এই মিশ্রণই prototype-কে নতুনদের কাছে বিভ্রান্তিকর করেছে। ভাবনার প্রশ্ন ১ আর ২-এ বিস্তারিত।

২ · [[Prototype]] বনাম prototype — সবচেয়ে বিভ্রান্তিকর নামকরণ

প্রথমেই সবচেয়ে বড় ধাঁধাটি পরিষ্কার করা যাক, কারণ এটি না বুঝলে বাকি সব গুলিয়ে যায়। JavaScript-এ “prototype” শব্দটি দুটি আলাদা জিনিস বোঝাতে ব্যবহার হয়:

  1. [[Prototype]] — প্রতিটি object-এর একটি লুকানো ঘর। ভাষার specification-এ ডাবল বর্গবন্ধনী দিয়ে লেখা নাম মানে internal slot — engine-এর ভেতরের তথ্য, যা obj.something লিখে সরাসরি পড়া যায় না। এর মান হয় আরেকটি object, অথবা null. এটিই সেই link, যা ধরে property খোঁজা হয়।
  2. Fn.prototype — শুধু function-এর একটি সাধারণ, দেখা যায় এমন property. এর একটিই কাজ: new Fn() দিয়ে যে নতুন object তৈরি হবে, তার [[Prototype]]-এ কোন object বসবে, তা বলে দেওয়া। একে বলব prototype property.
প্রশ্ন[[Prototype]]Fn.prototype
কার থাকেপ্রতিটি object-এর (array, function সহ)সাধারণ function আর class-এর (arrow function আর method shorthand-এর নেই)
কী এটিলুকানো internal slotসাধারণ property, Object.keys-এ না দেখালেও সরাসরি পড়া যায়
কী বোঝায়“আমি না পেলে কাকে জিজ্ঞেস করব”“আমাকে দিয়ে new করা object-রা কাকে জিজ্ঞেস করবে”
কীভাবে পড়বেনObject.getPrototypeOf(obj) (পুরোনো: obj.__proto__)Fn.prototype
উদাহরণObject.getPrototypeOf(rice) === Product.prototypeProduct.prototype.priceWithVat = …
Product function-এর নিজের ক্ষেত্রেFunction.prototype (কারণ Product নিজে একটি function)নতুন পণ্যদের জন্য রাখা object
মূল ধারণা

Product.prototype হলো Product-এর তৈরি object-দের prototype, Product-এর নিজের নয়। Product-এর নিজের [[Prototype]] হলো Function.prototype. এক বাক্যে: Object.getPrototypeOf(new Product()) === Product.prototype.

পড়া আর বদলানো — getPrototypeOf, setPrototypeOf

লুকানো ঘরটি পড়ার মানক উপায় Object.getPrototypeOf(obj) (ES5 থেকে)। বদলানোর উপায় Object.setPrototypeOf(obj, proto) (ES2015 থেকে):

const base = { greet() { return "আসসালামু আলাইকুম, " + this.name; } };
const user = { name: "Tania" };

Object.getPrototypeOf(user) === Object.prototype;  // true
Object.setPrototypeOf(user, base);                 // ⚠️ চলে, কিন্তু ধীর
user.greet();                                      // "আসসালামু আলাইকুম, Tania"
Object.getPrototypeOf(Object.prototype);           // null — শিকলের শেষ

setPrototypeOf কাজ করে, কিন্তু MDN স্পষ্ট করে সতর্ক করে যে এটি প্রতিটি engine-এ খুব ধীর একটি কাজ। কারণটি engine-এর ভেতরে: পর্ব ০৫-এ দেখেছেন, engine প্রতিটি object-এর গঠন একটি hidden class-এ লিখে রাখে, আর prototype সেই গঠনের অংশ। Property খোঁজার ফল সে মনে রাখে (inline cache) এই ধারণায় যে শিকল বদলাবে না। তৈরির পরে prototype বদলালে সেই ধারণা ভাঙে — object নতুন গঠনে যায়, মনে রাখা ফল বাতিল হয়, আর যে optimized কোড এই শিকলের ওপর নির্ভর করছিল তা deoptimization-এর শিকার হতে পারে। প্রভাব শুধু ওই লাইনে সীমিত থাকে না। তাই নিয়ম: prototype ঠিক করুন object তৈরির সময়েই — Object.create, new বা class দিয়ে।

__proto__ — পুরোনো দরজা, এখনো খোলা

পুরোনো কোড আর অনেক ব্লগে দেখবেন obj.__proto__. এটি একটি __proto__ accessor — Object.prototype-এ বসানো একটি getter/setter জোড়া, যা পড়লে [[Prototype]] দেয় আর লিখলে বদলায়। শুরুতে এটি ছিল Netscape/Mozilla-র নিজস্ব বৈশিষ্ট্য; ওয়েবের এত সাইট এটি ব্যবহার করত যে অন্য browser-ও নকল করে। ES2015 এটিকে মানদণ্ডে আনে, কিন্তু মূল অংশে নয় — Annex B-এ, যেখানে থাকে “শুধু পুরোনো ওয়েব চালু রাখার জন্য” রাখা বৈশিষ্ট্য।

নতুন কোডে __proto__ লিখবেন না। তিনটি কারণ: (১) এটি Object.prototype-এর ওপর নির্ভর করে, তাই Object.create(null) দিয়ে তৈরি object-এ নেই; (২) getPrototypeOf/setPrototypeOf একই কাজ পরিষ্কারভাবে করে; (৩) বাইরে থেকে আসা "__proto__" key এই setter-কে চালিয়ে দিতে পারে — এটিই পর্ব ১৪ আর ১৮-এ দেখা prototype pollution-এর মূল দরজা। একটি ব্যতিক্রম চোখে পড়বে: object literal-এর ভেতরে { __proto__: base, name: "x" } লেখা — এটি setter নয়, তৈরির সময়েই prototype ঠিক করার একটি বিশেষ ব্যাকরণ। কাজ করে, তবে Object.create বেশি পরিষ্কার।

৩ · Object.create — prototype বেছে নিয়ে object তৈরি

Prototype ঠিক করার সবচেয়ে সরাসরি উপায় Object.create(proto) (ES5): “একটি নতুন খালি object বানাও, যার [[Prototype]] হবে এই proto.” কোনো constructor নেই, কোনো new নেই — শুধু object আর link. Prototype-ভিত্তিক চিন্তার সবচেয়ে খাঁটি রূপ এটিই:

const productMethods = {
  priceWithVat() { return Math.round(this.price * 1.15); },
  label() { return `${this.name} — ৳${this.priceWithVat()}`; },
};

const rice = Object.create(productMethods);   // rice → productMethods → Object.prototype
rice.name = "চাল";
rice.price = 70;
rice.label();                                 // "চাল — ৳81"
Object.keys(rice);                            // [ 'name', 'price' ] — method নিজের নয়

rice-এর নিজের মাত্র দুটি property; label আর priceWithVat সে ধার করছে productMethods থেকে। label-এর ভেতরে this.priceWithVat()-ও কাজ করে, কারণ this হলো rice (ডটের বাঁ পাশে কে — পর্ব ২৪-এর মূল নিয়ম), আর rice থেকে আবার শিকল ধরে খোঁজা হয়। খেয়াল করুন: method prototype-এ থাকলেও this সবসময় যে object দিয়ে ডাকা হয়েছে সেটিই — এজন্যই একটি ভাগের method প্রতিটি পণ্যের নিজের দাম দিয়ে হিসাব করতে পারে। Object.create-এর দ্বিতীয় argument হিসেবে property descriptor-ও দেওয়া যায় (পর্ব ১৪), তবে বাস্তবে কম দেখা যায়।

Object.create(null) — কোনো উত্তরাধিকার নেই এমন dictionary

proto হিসেবে null দিলে এমন object তৈরি হয় যার শিকল সাথে সাথেই শেষ — Object.prototype-ও নেই। একে বলে null-prototype object. এতে toString, hasOwnProperty, constructor, __proto__ — কিছুই নেই। শুনতে অসুবিধার মনে হয়, কিন্তু “শব্দ → সংখ্যা” ধরনের dictionary-র জন্য ঠিক এটিই চাই:

  • লুকানো key নেই: সাধারণ {}-এ "constructor" in counts সত্য — যদিও আপনি কখনো সেই শব্দ গোনেননি। Null-prototype object-এ মিথ্যা।
  • "__proto__" সাধারণ key: সেখানে কোনো setter নেই, তাই user-এর লেখা "__proto__" শব্দটিও অন্য যেকোনো শব্দের মতো গোনা হয়, prototype বদলায় না।
  • যাচাই Object.hasOwn দিয়ে: object-এর নিজের hasOwnProperty method নেই, তাই Object.hasOwn (পর্ব ১৪) ব্যবহার করুন।

পর্ব ১৪-এ দেখা Object.groupBy-এর ফলাফল ঠিক এমন null-prototype object — একই কারণে। অংশ ৭-এর runnable উদাহরণে দেখবেন, একটি বাক্যে “constructor” আর “__proto__” শব্দ থাকলে সাধারণ {} দিয়ে শব্দ গোনা কীভাবে ভুল হয়, আর Object.create(null) কীভাবে ঠিক থাকে। আধুনিক কোডে এর আরেকটি বিকল্প পর্ব ১৮-এর Map — যেখানে key যেকোনো কিছু, আর inherited key-এর প্রশ্নই নেই।

৪ · Property খোঁজার নিয়ম — পড়া ওপরে যায়, লেখা যায় না

এবার engine-এর খোঁজার নিয়মটি পুরোপুরি লিখে ফেলা যাক। obj.key পড়লে:

  1. obj-এর own property-গুলোতে key আছে? থাকলে সেটির মান (getter হলে getter চালিয়ে ফল) — শেষ।
  2. না থাকলে p = obj-এর [[Prototype]]. p যদি null হয় → ফল undefined.
  3. না হলে p-এর own property-তে খোঁজা; না পেলে p-এর prototype — এভাবে শিকল ধরে।
  4. সাধারণ object-এর শিকল প্রায় সবসময় শেষ হয় Object.prototype → null-এ।

নিচে একটি Product পণ্য (অংশ ৫-এ বানাব) দিয়ে p.toString() ডাকলে কী হয়, ধাপে ধাপে দেখুন:

  1. p (নিজের ঘর)name, priceown property
  2. Product.prototypepriceWithVat, constructorসব পণ্যের ভাগের
  3. Object.prototypetoString, hasOwnProperty…সব object-এর ভাগের
  4. nullশিকলের শেষআর কোথাও নেই
ধাপ ১নিজের ঘরে খোঁজা
const p = new Product("চাল", 70);
p-এর own key: name, price
"toString" নেই → এক ধাপ ওপরে
ধাপ ২Product.prototype-এ খোঁজা
Object.getPrototypeOf(p) === Product.prototype  // true
এখানে আছে: priceWithVat, constructor
"toString" নেই → আরও এক ধাপ ওপরে
ধাপ ৩Object.prototype-এ পাওয়া গেল
Object.getPrototypeOf(Product.prototype) === Object.prototype  // true
toString পাওয়া গেল → খোঁজা শেষ
ধাপ ৪ডাকা হলো — this = p
Object.prototype.toString.call(p)  // কার্যত এটিই ঘটে
ফল: "[object Object]"
ধাপ ৫p.discount — কোথাও নেই
p → Product.prototype → Object.prototype → null
ফল: undefined  // error নয়
p.discount() লিখলে → TypeError: p.discount is not a function
  1. নিজের ঘর আগে। Engine প্রথমে p-এর own property দেখে — শুধু name আর price. toString নেই, তাই সে p-এর [[Prototype]] ধরে এক ধাপ ওপরে যায়।
  2. Product.prototype. new Product(…) দিয়ে তৈরি বলে p-এর prototype হলো Product.prototype. এখানে সব পণ্যের ভাগের priceWithVat আছে, কিন্তু toString নেই। আবার ওপরে।
  3. Object.prototype — পাওয়া গেল। Product.prototype নিজেও একটি সাধারণ object, তাই তার prototype Object.prototype. সেখানে toString আছে। খোঁজা থামে — আর ওপরে যাওয়ার দরকার নেই।
  4. ডাকার সময় this = p. Function পাওয়া গেল শিকলের তৃতীয় ধাপে, কিন্তু ডাকা হলো p.toString() হিসেবে — তাই ভেতরে this হলো p, Object.prototype নয়। “কোথায় পাওয়া গেল” আর “কাকে দিয়ে ডাকা হলো” — দুটি আলাদা প্রশ্ন। ফল "[object Object]".
  5. শিকলের শেষে undefined. p.discount কোথাও নেই; শিকল null-এ থামে আর ফল undefined. সেটিকে ডাকতে গেলে (p.discount()) তবেই TypeError আসে। Product.prototype-এ নিজের toString লিখলে খোঁজা দ্বিতীয় ধাপেই থামত — একেই বলে override.

লেখা শিকল ধরে ওপরে যায় না — shadowing

পড়া আর লেখার নিয়ম অসমান, আর এটিই অনেক বাগের উৎস। rice.priceWithVat = … লিখলে engine prototype-এর method বদলায় না; বরং rice-এর নিজের ঘরে একই নামের নতুন property তৈরি করে। পরের বার পড়লে খোঁজা প্রথম ধাপেই থেমে যায়, তাই prototype-এর method আড়ালে পড়ে — একে বলে property shadowing. (পর্ব ০৭-এর variable shadowing-এর মতোই ধারণা, শুধু scope-এর বদলে prototype chain-এ।)

1const rice = new Product("চাল", 70);2const oil = new Product("তেল", 180);3rice.priceWithVat = () => 0;4console.log(rice.priceWithVat(), oil.priceWithVat());5delete rice.priceWithVat;6console.log(rice.priceWithVat());
নিজের ঘর বনাম prototype
rice-এর own key
Product.prototype.priceWithVat
rice.priceWithVat পাওয়া যায়
oil.priceWithVat পাওয়া যায়
  1. শুরু। rice-এর নিজের দুটি ঘর — name, price. priceWithVat পড়লে খোঁজা যায় Product.prototype-এ, যেখানে VAT যোগ করার একটিমাত্র function আছে।
  2. আরেকটি পণ্য। oil-ও একই prototype-এর সাথে যুক্ত — একই function ভাগ করে।
  3. লেখা = নিজের ঘরে নতুন property. rice.priceWithVat = () => 0 prototype-এ যায় না; rice-এর ভেতরে তৃতীয় একটি own key তৈরি হলো। Prototype-এর আসল function একটুও বদলায়নি।
  4. আড়াল। এখন rice.priceWithVat() খোঁজা প্রথম ধাপেই নিজের function পায় → 0. কিন্তু oil-এর নিজের এমন কিছু নেই, তাই সে আগের মতোই prototype-এর function পায় → 207 (১৮০ × ১.১৫)।
  5. Delete করলে আড়াল সরে। delete rice.priceWithVat শুধু own property মোছে। delete কখনো prototype-এর property মোছে না (delete rice.toString কিছুই করে না)।
  6. আবার prototype-এর method. নিজের ঘর খালি হতেই খোঁজা আবার Product.prototype-এ যায় → 81 (৭০ × ১.১৫ = ৮০.৫, পূর্ণসংখ্যায় ৮১)।

দুটি সূক্ষ্ম ব্যতিক্রম জানা থাকা ভালো, কারণ ইন্টারভিউতে আসে:

  • শিকলে setter থাকলে সেটি চলে। Prototype-এ যদি price-এর জন্য একটি setter থাকে, তাহলে rice.price = 90 নতুন own property বানায় না — setter-টি চলে, this = rice. যাচাই বা রূপান্তরের নিয়ম এভাবেই ভাগ করা যায়; class-এর set (পর্ব ২৬) ঠিক এভাবে কাজ করে।
  • শিকলে read-only property থাকলে লেখা ব্যর্থ। Prototype-এর একই নামের property যদি writable: false হয়, তাহলে own property-ও তৈরি হয় না — সাধারণ script-এ চুপচাপ, strict mode-এ TypeError. এই আচরণকে কেউ কেউ “override mistake” বলেন; কেউ Object.prototype freeze করলে এটি হঠাৎ দেখা দেয়।

for…in বনাম Object.keys — শিকল কে দেখে

পর্ব ১২ আর ১৪-এ বলা হয়েছিল, for…in inherited property-ও দেখায়। এখন কারণটি পরিষ্কার: for…in পুরো শিকল ঘুরে সব enumerable string key তোলে, আর Object.keys শুধু own enumerable key. Built-in method-গুলো (যেমন toString) enumerable নয় বলে সাধারণত চোখে পড়ে না। কিন্তু Product.prototype.priceWithVat = function… লিখে বসানো method enumerable — সাধারণ = দিয়ে তৈরি সব property-র মতো। তাই for (const k in rice) দেবে name, price, priceWithVat; আর Object.keys(rice) দেবে শুধু [ 'name', 'price' ]. পর্ব ২৬-এর class-এর method স্বয়ংক্রিয়ভাবে non-enumerable — পুরোনো ধরনের তুলনায় class-এর একটি ছোট কিন্তু বাস্তব উন্নতি।

৫ · Constructor function আর new — একই ধরনের অনেক object

একটি দোকানে শত শত পণ্য। প্রতিটির জন্য হাতে Object.create আর property বসানো ক্লান্তিকর। ES2015-এর আগে (আর এখনো অনেক পুরোনো লাইব্রেরিতে) এর সমাধান ছিল constructor function: একটি সাধারণ function, যা নিয়ম অনুযায়ী বড় হাতের অক্ষর দিয়ে শুরু হয় আর new দিয়ে ডাকা হয়:

function Product(name, price) {      // নিয়ম: নাম বড় হাতের অক্ষরে
  this.name = name;                  // ডেটা → প্রতিটি object-এর নিজের
  this.price = price;
}
Product.prototype.priceWithVat = function () {   // আচরণ → prototype-এ, একবার
  return Math.round(this.price * 1.15);
};

const p = new Product("চাল", 70);
p.priceWithVat();                    // 81

new Product("চাল", 70) লেখার সময় আসলে চারটি কাজ হয়। এগুলো মুখস্থ রাখুন — বিশ্ববিদ্যালয়ের লিখিত পরীক্ষা থেকে front-end ইন্টারভিউ পর্যন্ত সবখানে আসে:

1function Product(name, price) {2  this.name = name;3  this.price = price;4}5Product.prototype.priceWithVat = function () { … };6const p = new Product("চাল", 70);7console.log(p.priceWithVat());
new-এর ভেতরে কী ঘটছে
নতুন object
তার [[Prototype]]
this (Product-এর ভেতরে)
p
  1. প্রস্তুতি। Product function তৈরি হওয়ার সময়েই engine তার জন্য একটি Product.prototype object বানিয়ে রাখে (ভেতরে শুধু constructor). আমরা তাতে priceWithVat যোগ করলাম। এখনো কোনো পণ্য নেই।
  2. ধাপ ১ — নতুন খালি object তৈরি। new প্রথমে একটি একদম নতুন, খালি object বানায়।
  3. ধাপ ২ — prototype যুক্ত করা। নতুন object-এর [[Prototype]]-এ বসানো হয় Product.prototype. (Specification-এ ধাপ ১ আর ২ একসাথে ঘটে — object তৈরিই হয় সঠিক prototype নিয়ে।) Product.prototype যদি object না হয়, তখন Object.prototype বসে।
  4. ধাপ ৩ — this = নতুন object ধরে function ডাকা। Product-এর শরীর চলে, আর this.name = name লাইনগুলো নতুন object-এর own property বানায়। Constructor-এর কাজ শুধু ডেটা বসানো।
  5. ধাপ ৪ — ফেরত দেওয়া। Function যদি নিজে কোনো object return না করে (সাধারণ ক্ষেত্রে করে না), new নতুন object-টিই ফেরত দেয় — সেটি p-এ বসে। Constructor নিজে কোনো object return করলে সেটিই ফল হয়, আর ধাপ ১–৩-এর object ফেলে দেওয়া হয় (primitive return করলে উপেক্ষিত)। তারপর p.priceWithVat() শিকল ধরে prototype-এর function পায় → 81.

new ভুলে গেলে কী হয়? const x = Product("চাল", 70) — এখন এটি সাধারণ function call. ধাপ ১, ২, ৪ কিছুই হয় না; x হয় undefined. আর this? সাধারণ script-এ global object — অর্থাৎ window.name আর window.price চুপচাপ বদলে যায়! Strict mode-এ this হয় undefined, তাই this.name = … লাইনে TypeError — অন্তত ভুলটি ধরা পড়ে। পর্ব ২৬-এর class-কে new ছাড়া ডাকলে সবসময় TypeError আসে — আরেকটি কারণ যে আধুনিক কোডে class পছন্দ।

Method কোথায় রাখবেন — প্রতিটি object-এ, নাকি prototype-এ?

নতুনরা (আর AI-ও) প্রায়ই constructor-এর ভেতরেই method লেখে:

function ProductBad(name, price) {
  this.name = name;
  this.price = price;
  this.priceWithVat = function () {          // ❌ প্রতিটি object-এর জন্য নতুন function
    return Math.round(this.price * 1.15);
  };
}
const a = new ProductBad("চাল", 70), b = new ProductBad("ডাল", 120);
a.priceWithVat === b.priceWithVat;           // false — দুটি আলাদা function
// Product দিয়ে একই তুলনা → true — একটিই function

কাজ একই, কিন্তু খরচ আলাদা। ১০,০০০ পণ্য মানে ১০,০০০টি function object, প্রতিটি heap-এ জায়গা নেয় আর garbage collector-কে কাজ দেয়। Prototype-এ রাখলে function একটিই। আরও একটি পার্থক্য: পরে সব পণ্যের আচরণ বদলাতে চাইলে (যেমন VAT-এর হার) prototype-এ এক জায়গায় বদলালেই হয়; প্রতি-object কপিতে পুরোনো object-গুলো পুরোনো function নিয়েই থাকে। Constructor-এর ভেতরে function রাখার একটিই বৈধ কারণ — closure (পর্ব ২২) দিয়ে সত্যিকারের গোপন ডেটা রাখা। আজ সেই কাজ class-এর #private field (পর্ব ২৬) আরও ভালোভাবে করে।

constructor property — prototype থেকে ফেরার পথ

প্রতিটি সাধারণ function-এর prototype object-এ শুরু থেকেই একটি property থাকে: Product.prototype.constructor === Product. একে বলে constructor property. যেহেতু সব পণ্য prototype থেকে ধার করে, p.constructor-ও Product দেয় — “কোন constructor আমাকে বানিয়েছে” জানার একটি সহজ (তবে নির্ভরযোগ্য নয়) উপায়। নির্ভরযোগ্য নয় কারণ এটি একটি সাধারণ, বদলানো যায় এমন property. বিশেষ করে Product.prototype = { … } লিখে পুরো prototype নতুন object দিয়ে বদলালে constructor হারিয়ে যায় (তখন শিকল ধরে Object পাওয়া যায়) — অংশ ৬-এ দেখবেন কীভাবে আবার বসাতে হয়।

৬ · instanceof, isPrototypeOf আর দুই constructor-এর মধ্যে উত্তরাধিকার

“এই object কি একটি পণ্য?” — প্রশ্নটির দুটি উত্তর-যন্ত্র আছে, আর দুটিই শিকল ধরে হাঁটে:

লেখাপ্রশ্নটি আসলেকখন
p instanceof ProductProduct.prototype কি p-এর শিকলের কোথাও আছে?constructor বা class হাতে থাকলে
Product.prototype.isPrototypeOf(p)এই object কি p-এর শিকলের কোথাও আছে?constructor ছাড়া, শুধু Object.create-এর prototype object থাকলে
Object.getPrototypeOf(p) === Product.prototypeসরাসরি prototype কি এটি? (শুধু এক ধাপ)“ঠিক এই ধরন, উপ-ধরন নয়” জানতে

instanceof-এর নাম শুনে মনে হয় এটি “কোন class থেকে তৈরি” দেখে — আসলে দেখে শুধু শিকল। তাই Product.prototype পরে অন্য object দিয়ে বদলালে পুরোনো পণ্যগুলো হঠাৎ instanceof Product-এ false দেয়। আর Object.create(productMethods) দিয়ে তৈরি object-এর জন্য কোনো constructor নেই, তাই সেখানে isPrototypeOf লাগে: productMethods.isPrototypeOf(rice). আরও এক ধাপ গভীরে: ES2015 থেকে instanceof প্রথমে ডান পাশের object-এ Symbol.hasInstance method খোঁজে; থাকলে সেটিই উত্তর ঠিক করে, না থাকলে স্বাভাবিক শিকল-হাঁটা। এই Symbol-গুলোর গল্প পর্ব ২৮-এ।

দুই constructor-এর উত্তরাধিকার — ES2015-এর আগের পদ্ধতি

এবার একটি ডিজিটাল পণ্য — ই-বুক, যার দাম আর VAT-এর হিসাব সাধারণ পণ্যের মতোই, কিন্তু আছে ফাইলের আকার। আমরা চাই শিকল হোক: ebook → DigitalProduct.prototype → Product.prototype → Object.prototype. এই prototypal inheritance-এর জন্য দুটি কাজ লাগে: (১) parent constructor-কে নতুন object-এর ওপর ডাকা, যাতে name আর price বসে; (২) DigitalProduct.prototype-কে এমন object বানানো, যার prototype Product.prototype. সব একসাথে চালিয়ে দেখুন:

function Product(name, price) {
  this.name = name;
  this.price = price;
}
Product.prototype.priceWithVat = function () {
  return Math.round(this.price * 1.15);
};
const rice = new Product("চাল", 70);
const oil = new Product("তেল", 180);
console.log(rice.priceWithVat(), oil.priceWithVat());
console.log(rice.priceWithVat === oil.priceWithVat, Object.hasOwn(rice, "priceWithVat"));

function DigitalProduct(name, price, sizeMB) {
  Product.call(this, name, price);          // parent-এর ঘরগুলো এই object-এ
  this.sizeMB = sizeMB;
}
DigitalProduct.prototype = Object.create(Product.prototype);
DigitalProduct.prototype.constructor = DigitalProduct;
const ebook = new DigitalProduct("ই-বুক", 200, 5);
console.log(ebook.priceWithVat(), ebook instanceof Product, ebook.constructor.name);

rice.priceWithVat = () => 0;                 // shadowing: নিজের property তৈরি
console.log(rice.priceWithVat(), oil.priceWithVat(), Object.hasOwn(rice, "priceWithVat"));

প্রথম লাইন 81 207 — দুটি পণ্য একই function দিয়ে নিজের নিজের দাম হিসাব করল। দ্বিতীয় লাইন true false: দুই পণ্যের function হুবহু একই object, আর সেটি কারও own property নয়। তৃতীয় লাইন 230 true DigitalProduct: ebook-এ priceWithVat নেই, DigitalProduct.prototype-এও নেই, পাওয়া গেল এক ধাপ ওপরে Product.prototype-এ (২০০ × ১.১৫ = ২৩০)। Product.call(this, …) হলো পর্ব ২৪-এর call — parent constructor-কে নতুন object-এর ওপর চালানো; class-এ এর নাম super(…). constructor আবার বসানোর লাইনটি বাদ দিলে ebook.constructor.name হতো "Product", কারণ Object.create-এর নতুন object-এ constructor নেই, শিকল ধরে Product-এরটি আসত। শেষ লাইন 0 207 true — অংশ ৪-এর shadowing, এবার নিজের চোখে।

লক্ষ করুন, DigitalProduct.prototype = new Product() লেখা পুরোনো একটি ভুল পদ্ধতি — এতে parent constructor অকারণে চলে, আর prototype-এ name: undefined-এর মতো আবর্জনা বসে। Object.create(Product.prototype) শুধু link তৈরি করে, কিছু চালায় না — ES5 এই কাজের জন্যই Object.create এনেছিল (ভাবনার প্রশ্ন ৩)। আর এত লাইনের এই পদ্ধতিই পর্ব ২৬-এ হয়ে যাবে মাত্র class DigitalProduct extends Product { constructor(…) { super(…); … } } — ভেতরে ঠিক একই শিকল।

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

Java আর C++-এ উত্তরাধিকার class-ভিত্তিক: compile করার সময়েই কাঠামো স্থির, আর method খোঁজা হয় class-এর টেবিলে (C++-এর vtable). JavaScript-এ উত্তরাধিকার হলো চলার সময় object থেকে object-এ delegation — শিকল যেকোনো সময় বদলানো যায় (Java-র inheritance). Python আসলে কাছাকাছি: obj.attr পড়লে আগে instance-এর __dict__, তারপর class আর MRO ধরে parent class-গুলোর __dict__ খোঁজা হয় — প্রায় prototype chain-এর মতো; আর instance-এ একই নামের attribute বসালে class-এর attribute আড়াল হয়, ঠিক shadowing (Python-এর inheritance). এর কোনোটি না জানলে এই বাক্সটি বাদ দিন।

৭ · Built-in chain — array, function, string আর built-in বদলানোর বিপদ

আপনি যে [1, 2, 3].map(…), greet.call(…) বা "abc".toUpperCase() লেখেন — এগুলো সবই একই ব্যবস্থা। প্রতিটি built-in ধরনের একটি prototype আছে, যেখানে তার method-গুলো থাকে। ধাপে ধাপে তিনটি শিকল পাশাপাশি দেখুন:

nums = [1, 2, 3]own: 0, 1, 2, length
function greet() {}own: name, length, prototype
"abc" (primitive)নিজের কোনো property নেই
Array.prototypemap, push, at, filter…
Function.prototypecall, apply, bind
String.prototypetoUpperCase, slice, at…
Object.prototypetoString, hasOwnProperty, valueOf
nullশিকলের শেষ
  1. তিনটি মান। একটি array, একটি function আর একটি string. Array-র own property শুধু index আর length; function-এর name, length আর (সাধারণ function বলে) prototype. String একটি primitive — তার নিজের কোনো property-ই নেই।
  2. প্রথম ধাপের prototype. nums.map নিজের ঘরে নেই → Array.prototype-এ পাওয়া যায়। greet.bind → Function.prototype-এ (পর্ব ২৪-এর call/apply/bind এখানেই থাকে)। এজন্য Object.hasOwn(nums, "map") হলো false, কিন্তু "map" in nums হলো true.
  3. সবার ওপরে Object.prototype. Array.prototype আর Function.prototype নিজেরা সাধারণ object-এর মতো, তাই তাদের prototype Object.prototype. এজন্য array আর function-ও hasOwnProperty পায়। “JavaScript-এ প্রায় সবকিছুই object” কথাটির অর্থ এটিই।
  4. শেষে null. Object.getPrototypeOf(Object.prototype) হলো null. এই object-এর prototype বদলানোও যায় না — চেষ্টা করলে TypeError.
  5. Primitive কীভাবে method পায়? "abc".toUpperCase() লিখলে engine মুহূর্তের জন্য একটি String wrapper object বানায় (autoboxing, পর্ব ০৬), যার prototype String.prototype — সেখান থেকে method পাওয়া যায়, তারপর wrapper ফেলে দেওয়া হয়। তাই "abc".x = 1 লিখলে মান টেকে না — wrapper-টি তখনই হারিয়ে যায়। (Engine বাস্তবে wrapper না বানিয়েই এটি optimize করে, কিন্তু আচরণ এমনই।) null আর undefined-এর কোনো wrapper নেই — তাই null.x-এ TypeError.

এবার শিকলটি কোড দিয়ে হেঁটে দেখি, আর অংশ ৩-এর প্রতিশ্রুতি মতো একটি বাক্যের শব্দ গুনি — সাধারণ {} আর Object.create(null) দুটো দিয়েই:

const nums = [1, 2, 3];
function chainOf(value) {
  const names = [];
  let p = Object.getPrototypeOf(value);
  while (p !== null) {
    names.push(p.constructor.name);
    p = Object.getPrototypeOf(p);
  }
  return names;
}
console.log(chainOf(nums), chainOf(chainOf), chainOf("abc"));
console.log(Object.hasOwn(nums, "map"), "map" in nums, Object.hasOwn(nums, "length"));

const words = "আম জাম আম constructor __proto__ আম".split(" ");
const plain = {};
const safe = Object.create(null);
for (const w of words) {
  plain[w] = (plain[w] ?? 0) + 1;
  safe[w] = (safe[w] ?? 0) + 1;
}
console.log(Object.keys(plain), typeof plain.constructor);
console.log(JSON.stringify(safe), "toString" in safe);

প্রথম লাইন [ 'Array', 'Object' ] [ 'Function', 'Object' ] [ 'String', 'Object' ] — chainOf প্রতিটি prototype-এর constructor.name লিখে শিকল ধরে null পর্যন্ত হাঁটে (string-এর বেলায় getPrototypeOf নিজেই primitive-কে wrapper-এ বদলে নেয়)। দ্বিতীয় লাইন false true true — map ধার করা, length array-র নিজের। এবার শব্দ গোনা। সাধারণ {}-এর ফল [ 'আম', 'জাম', 'constructor' ] string — দুটি ভুল একসাথে! plain["constructor"] শুরুতেই inherited Object function দিল, ?? 0 কাজে এল না, আর function + 1 হয়ে গেল একটি লম্বা string. আর "__proto__" শব্দটি key হিসেবে বসলই না — সেটি গিয়ে চালাল __proto__-এর setter (string পেয়ে সে কিছু না করে ফিরে গেল)। শেষ লাইন {"আম":3,"জাম":1,"constructor":1,"__proto__":1} false — null-prototype object-এ সব শব্দ ঠিকঠাক গোনা হলো, আর toString-এর মতো কোনো লুকানো key নেই। User-এর দেওয়া লেখা থেকে key বানালে এই পার্থক্যই নিরাপত্তার পার্থক্য।

Built-in prototype বদলানো — কেন করবেন না

যেহেতু সব array Array.prototype থেকে ধার করে, সেখানে একটি method বসালে সব array সাথে সাথে তা পায়: Array.prototype.last = function () { return this[this.length - 1]; }. লোভনীয়, তাই না? একে বলে monkey patching, আর built-in-এর ক্ষেত্রে এটি প্রায় সবসময় খারাপ সিদ্ধান্ত:

  • সংঘর্ষ: আপনার পাতায় দুটি লাইব্রেরি যদি একই নামে আলাদা আচরণের method বসায়, যেটি পরে লোড হয় সেটি জেতে — অন্যটি চুপচাপ ভেঙে যায়।
  • ভবিষ্যৎ: ভাষা যদি পরে একই নামে method আনে (যেমন ES2022 আনল at), আপনার কোড আর মানক method — দুটির একটি ভুল আচরণ করে। ২০১৮ সালে ঠিক এ কারণে প্রস্তাবিত Array.prototype.flatten-এর নাম বদলে flat রাখতে হয়েছিল, কারণ MooTools নামের পুরোনো লাইব্রেরি ব্যবহার করা সাইট ভাঙছিল — ঘটনাটি “SmooshGate” নামে পরিচিত (ভাবনার প্রশ্ন ৫)।
  • for…in দূষণ: = দিয়ে বসানো method enumerable, তাই পাতার সব for…in লুপে হঠাৎ last নামের একটি key দেখা দেয়।

একটিমাত্র বৈধ ব্যতিক্রম আছে: polyfill. পুরোনো browser-এ নেই এমন একটি মানক method, মানদণ্ডে লেখা হুবহু আচরণে যোগ করা — আর শুধু তখনই, যখন সেটি আগে থেকে নেই:

if (!Array.prototype.at) {                       // ১. আগে থেকে থাকলে হাত দেবেন না
  Object.defineProperty(Array.prototype, "at", {  // ২. non-enumerable, মানক নাম
    value(i) {                                    // ৩. specification-এর আচরণ
      const n = Math.trunc(i) || 0;
      const k = n >= 0 ? n : this.length + n;
      return k >= 0 && k < this.length ? this[k] : undefined;
    },
    writable: true, configurable: true,
  });
}

এটি একটি সরল উদাহরণ (আসল মানক at array-like object-এও কাজ করে)। বাস্তবে polyfill নিজে না লিখে core-js-এর মতো পরীক্ষিত লাইব্রেরি বা build tool (পর্ব ৪৬) দিয়ে যোগ করুন। আর যা মানক নয় — নিজের helper — তা লিখুন সাধারণ function হিসেবে: last(arr), arr.last() নয়।

৮ · পারফরম্যান্স — শিকল স্থির রাখুন

“প্রতিবার শিকল ধরে খুঁজতে হলে তো ধীর হওয়ার কথা” — যুক্তিসংগত প্রশ্ন। বাস্তবে আধুনিক engine খুব কমই আসলে হাঁটে। পর্ব ০৫-এর hidden class আর inline cache এখানে কাজ করে: কোনো জায়গায় (যেমন একটি লুপের ভেতরে p.priceWithVat()) প্রথমবার খোঁজার পরে engine মনে রাখে, “এই গঠনের object-এর জন্য priceWithVat পাওয়া যায় দুই ধাপ ওপরের ওই prototype-এ”। পরের হাজারবার সে সরাসরি সেখানে যায়। V8 prototype object-গুলোর জন্য আলাদা হিসাব রাখে, যাতে কেউ শিকলের কোনো prototype বদলালে সংশ্লিষ্ট মনে রাখা ফলগুলো বাতিল করা যায়।

এখান থেকেই ব্যবহারিক নিয়মগুলো আসে:

✅ যা করবেন

Prototype ঠিক করুন তৈরির সময়ে (new, class, Object.create). Prototype-এর method শুরুতে, একবারে বসান। Constructor-এ সব own property একই ক্রমে বসান — একই ধরনের সব object একই hidden class পায়।

⚠️ যা এড়াবেন

তৈরির পরে Object.setPrototypeOf বা obj.__proto__ = …; চলার মাঝপথে built-in prototype-এ method যোগ; গরম লুপের ভেতরে prototype বদল। এগুলো engine-এর মনে রাখা ফল বাতিল করে, আর optimized কোড deoptimize হতে পারে।

তবে মনে রাখুন: এগুলো engine-এর বাস্তবায়নের খুঁটিনাটি, ভাষার নিয়ম নয়। সাধারণ অ্যাপে কয়েক ধাপের শিকল কোনো সমস্যাই নয়। আগে পরিষ্কার কোড, তারপর DevTools-এ মেপে দেখে (পর্ব ৫৩) optimization — অনুমানে নয়।

পরীক্ষা ও ইন্টারভিউতে
  • __proto__ আর prototype-এর পার্থক্য? — __proto__ (আসলে [[Prototype]]) প্রতিটি object-এর লুকানো link, যা ধরে property খোঁজা হয়; prototype শুধু function-এর property, যা new-এ তৈরি object-এর [[Prototype]] হয়।
  • new কী কী করে? — নতুন object তৈরি, তার prototype = Fn.prototype, this = সেই object ধরে Fn চালানো, Fn object return না করলে নতুন object ফেরত।
  • instanceof কীভাবে কাজ করে? — বাঁ পাশের object-এর prototype chain-এ ডান পাশের .prototype আছে কি না দেখে (আগে Symbol.hasInstance দেখে)।
  • Object.create(null) কেন? — কোনো inherited key ছাড়া dictionary; "constructor" বা "__proto__"-এর মতো key নিরাপদে রাখা যায়।
  • Prototype-এর method-এ মান বসালে কী হয়? — নিজের ঘরে নতুন property তৈরি হয় (shadowing); prototype বদলায় না, যদি না শিকলে setter বা read-only property থাকে।

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

AI টুল বিশাল পরিমাণ পুরোনো কোড আর ব্লগ পড়ে শিখেছে, যার বড় অংশ ২০১৫-এর আগের। তাই prototype-এর ক্ষেত্রে AI প্রায়ই পুরোনো অভ্যাস ফিরিয়ে আনে — কোড চলে, কিন্তু ধীর, ভঙ্গুর বা অনিরাপদ। সবচেয়ে সাধারণ ভুলগুলো:

AI-এর ভুলকেন সমস্যাকী চাইবেন
Array.prototype.sum = … বা String.prototype.capitalize = …লাইব্রেরি আর ভবিষ্যৎ মানকের সাথে সংঘর্ষ, for…in দূষণসাধারণ helper function: sum(arr)
obj.__proto__ = baselegacy accessor, ধীর, null-prototype object-এ নেইতৈরির সময় Object.create(base) বা class
[[Prototype]] আর prototype গুলিয়ে ব্যাখ্যা (“object-এর prototype property”)ভুল ধারণা ছড়ায়; obj.prototype সাধারণ object-এ undefinedObject.getPrototypeOf(obj) দিয়ে যাচাই
Constructor-এর ভেতরে this.method = function…প্রতিটি object-এ নতুন function — মেমরি নষ্টmethod prototype-এ, বা class-এর method
Child.prototype = new Parent()parent অকারণে চলে, prototype-এ আবর্জনা ডেটাObject.create(Parent.prototype), বা extends
Prototype বদলে constructor আবার না বসানোobj.constructor ভুল constructor দেখায়constructor আবার বসানো, বা class
বাইরের key দিয়ে সাধারণ {}-এ লেখা, বা deep merge-এ key যাচাই না করাprototype pollutionMap বা Object.create(null); __proto__, constructor, prototype key বাদ

AI-কে object-এর ধরন বানাতে বললে কোথায় কী থাকবে, তা আগেই বলে দিন:

একটি অনলাইন দোকানের জন্য JavaScript-এ Product আর DigitalProduct ধরন বানিয়ে দাও। নিয়ম: ১. আধুনিক class syntax ব্যবহার করবে; DigitalProduct হবে extends Product. ২. priceWithVat()-এর মতো method শুধু class-এর শরীরে থাকবে — constructor-এর ভেতরে this.method = function লিখবে না। ৩. Array.prototype, Object.prototype বা অন্য কোনো built-in prototype-এ কিছু যোগ করবে না; দরকারি helper সাধারণ function হবে। ৪. __proto__ বা Object.setPrototypeOf ব্যবহার করবে না। ৫. user-এর দেওয়া key (যেমন পণ্যের tag গোনা) রাখতে Map বা Object.create(null) ব্যবহার করবে। ৬. শেষে console.log দিয়ে দেখাও: দুটি পণ্যের priceWithVat একই function কি না (===), ebook instanceof Product, আর Object.getPrototypeOf(DigitalProduct.prototype) === Product.prototype. ৭. প্রতিটি নিয়ম কোডের কোথায় মানা হয়েছে, এক লাইনে ব্যাখ্যা দাও।

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

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

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

প্র ০১Class ছাড়া object-oriented programming — Self ভাষা (১৯৮৭) কী প্রস্তাব করেছিল, আর “prototype-ভিত্তিক” নকশা class-ভিত্তিক নকশা থেকে মৌলিকভাবে কোথায় আলাদা?

১৯৮০-এর দশকে object-oriented programming মানে ছিল Smalltalk-এর ধারা: প্রতিটি object কোনো একটি class-এর instance, আর class-ও একটি object যার নিজের class আছে (metaclass). কাঠামোটি শক্তিশালী, কিন্তু জটিল — একটি সাধারণ জিনিস বোঝাতে class, instance, metaclass, inheritance hierarchy — অনেক স্তর।

১৯৮৬ সালে Henry Lieberman একটি গবেষণাপত্রে দেখান যে ভাগের আচরণ class ছাড়াও বাস্তবায়ন করা যায়: একটি object যা জানে না, তা সে আরেকটি “prototype” object-কে পাঠিয়ে দেয় — delegation. পরের বছর, ১৯৮৭ সালে, David Ungar আর Randall Smith (Self-এর নকশা শুরু হয়েছিল Xerox PARC-এ) “Self: The Power of Simplicity” নামের গবেষণাপত্রে Self ভাষা প্রকাশ করেন। Self-এ শুধু object আছে, আর প্রতিটি object-এ নাম দেওয়া “slot”. কিছু slot-কে “parent” হিসেবে চিহ্নিত করা যায়; কোনো message-এর উত্তর নিজের slot-এ না পেলে object সেটি parent-এর কাছে পাঠায়। নতুন object বানাতে একটি বিদ্যমান object নকল (clone) করতে হয়।

মৌলিক পার্থক্যটি দার্শনিক। Class-ভিত্তিক জগতে আগে বিমূর্ত বিবরণ (“পণ্য কী”), তারপর নির্দিষ্ট জিনিস। Prototype-ভিত্তিক জগতে আগে একটি নির্দিষ্ট জিনিস (“এই চালের প্যাকেট”), আর নতুন জিনিস হলো “এটির মতো, শুধু এই পার্থক্যটুকু”। মানুষ আসলে প্রায়ই এভাবেই ভাবে — একটি উদাহরণ থেকে। Self-এর নকশাকাররা পরে দেখান, একটি সাধারণ object-কে “traits” object হিসেবে ভাগের method রাখতে ব্যবহার করলে class-এর প্রায় সব সুবিধা পাওয়া যায় — ঠিক যেমন JavaScript-এ Product.prototype.

আরেকটি পার্থক্য: class-ভিত্তিক ভাষায় সাধারণত object তৈরির পরে তার class বদলায় না। Delegation-এ link সাধারণ একটি মান, তাই চলার সময়েই বদলানো যায়, আর parent-এ যোগ করা method সাথে সাথে সব child পায়। Self-এর প্রভাব শুধু JavaScript-এ নয় — Lua-র metatable-এর __index, Io ভাষা — সবখানে একই delegation ধারণা।

মূল উপলব্ধি: Prototype-ভিত্তিক নকশা বলে, “ভাগের আচরণ পেতে বিমূর্ত ছাঁচ লাগে না — একটি নির্দিষ্ট object-এর দিকে link-ই যথেষ্ট।” JavaScript-এর class লিখলেও ভেতরে আপনি এই দর্শনেই কাজ করছেন।

প্র ০২১৯৯৫ সালে Brendan Eich কেন prototype বেছে নিলেন, আর “দেখতে Java-র মতো হতে হবে” — এই চাপ থেকে কীভাবে new আর constructor function এল?

১৯৯৫ সালের বসন্তে Netscape Brendan Eich-কে নিয়োগ দেয় browser-এর জন্য একটি scripting ভাষা বানাতে। Eich-এর আগ্রহ ছিল Scheme-এর মতো একটি ভাষা। কিন্তু একই সময়ে Netscape Sun Microsystems-এর সাথে Java-কে browser-এ আনার চুক্তি করছিল। ব্যবস্থাপনার সিদ্ধান্ত: নতুন ভাষাটি হবে Java-র “ছোট ভাই” — ওয়েব ডিজাইনারদের জন্য সহজ script, আর তাকে দেখতে Java-র মতো হতে হবে। নামও শেষে হলো JavaScript (প্রথমে Mocha, তারপর LiveScript), যদিও দুটি ভাষা আসলে খুব আলাদা।

Eich-এর হাতে সময় ছিল খুব কম — প্রথম সংস্করণ তিনি প্রায় দশ দিনে লিখেছিলেন বলে তিনি নিজে বলেছেন। এই অবস্থায় Java-র মতো পূর্ণ class ব্যবস্থা (class ঘোষণা, type, access control) বানানো সম্ভব ছিল না, আর Java-র সাথে সরাসরি প্রতিযোগিতা করাও কাম্য ছিল না। Self-এর prototype মডেল ছিল ছোট আর সরল: object, property আর একটি link — বাস্তবায়ন করতে কম কোড, আর ছোট script-এ নমনীয়।

কিন্তু “Java-র মতো দেখাতে হবে” চাপের ফলে উপরে একটি Java-সদৃশ পোশাক পরানো হলো। Java-তে object বানাতে লেখা হয় new Product(…); তাই JavaScript-এও new এল। কিন্তু class ছিল না, তাই new-এর পরে বসল একটি সাধারণ function — constructor function — আর তার prototype property হলো ভাগের method রাখার জায়গা। ফল হলো একটি অদ্ভুত সংকর: ভেতরে Self-এর delegation, বাইরে Java-র চেহারা। Douglas Crockford পরে এই ধরনকে বলেছেন “pseudoclassical” — class-এর মতো দেখায়, কিন্তু class নয়।

এই সংকরের দাম অনেক বছর ধরে দিতে হয়েছে। Java থেকে আসা প্রোগ্রামাররা ভাবতেন Product একটি class, অথচ উত্তরাধিকারের জন্য লিখতে হতো Child.prototype = Object.create(Parent.prototype)-এর মতো অচেনা লাইন। new ভুলে গেলে global object দূষিত হতো। প্রতিটি লাইব্রেরি (Prototype.js, MooTools, YUI) নিজের “class” helper বানাত। শেষে ES2015-এর class keyword এই চেহারাটিকে পরিষ্কার করল — কিন্তু ভেতরের মডেল বদলায়নি।

মূল উপলব্ধি: JavaScript-এর prototype ব্যবস্থার বিভ্রান্তি প্রযুক্তিগত নয়, ঐতিহাসিক — একটি সরল মডেলের ওপর ব্যবসায়িক কারণে অন্য ভাষার চেহারা চাপানো হয়েছিল। এটি জানলে new, prototype আর class-এর সম্পর্ক অনেক যৌক্তিক মনে হয়।

প্র ০৩Object.create এসেছে ES5-এ (২০০৯)। তার আগে “এই object-কে prototype ধরে নতুন object বানাও” — এত সরল কাজটি কেন সরাসরি করা যেত না, আর Douglas Crockford-এর “beget” ধারণা কী ছিল?

ES3 (১৯৯৯)-এর JavaScript-এ একটি object-এর prototype ঠিক করার মানক উপায় ছিল একটিই: constructor function আর new. আপনার হাতে যদি শুধু একটি object থাকে — ধরুন productMethods — আর আপনি চান এমন একটি নতুন object যার prototype এটি, তাহলে কোনো সরাসরি পথ ছিল না। Mozilla-র __proto__ ছিল, কিন্তু মানক নয়, আর Internet Explorer-এ ছিল না।

Douglas Crockford ২০০৬ সালের দিকে “Prototypal Inheritance in JavaScript” নামের একটি লেখায় একটি ছোট কৌশল জনপ্রিয় করেন: একটি খালি অস্থায়ী function বানানো, তার prototype-এ কাঙ্ক্ষিত object বসানো, তারপর new দিয়ে ডাকা। function object(o) { function F() {} F.prototype = o; return new F(); }. এটি আসলে new-এর ধাপ ২-কে (prototype যুক্ত করা) অন্য ধাপগুলো থেকে আলাদা করে ব্যবহারের কৌশল। তাঁর ২০০৮ সালের বই “JavaScript: The Good Parts”-এ একই ধারণা আসে Object.beget নামে — “beget” মানে জন্ম দেওয়া: একটি object আরেকটি object-এর জন্ম দেয়, মাঝে কোনো class ছাড়াই। তাঁর যুক্তি ছিল, JavaScript-এর আসল শক্তি prototypal, তাই pseudoclassical চেহারা বাদ দিয়ে সরাসরি object থেকে object বানানোই স্বাভাবিক।

ES4-এর বিশাল, class-ভিত্তিক পরিকল্পনা ২০০৮ সালে বাতিল হওয়ার পর কমিটি ছোট, ব্যবহারিক ES3.1-এ মনোযোগ দেয়, যা প্রকাশিত হয় ES5 (ডিসেম্বর ২০০৯) নামে। সেখানে Object.create(proto, descriptors) মানক হলো, সাথে Object.getPrototypeOf. Crockford-এর কৌশলের তুলনায় এর দুটি সুবিধা: দ্বিতীয় argument-এ property descriptor দেওয়া যায়, আর Object.create(null) — যা কোনো polyfill দিয়ে পুরোপুরি নকল করা যায় না, কারণ F.prototype = null দিলে new নিয়ম অনুযায়ী Object.prototype বসিয়ে দেয় (অংশ ৫-এর ধাপ ২)।

আজ Object.create-এর সবচেয়ে বড় দুটি ব্যবহার: ES2015-এর আগের উত্তরাধিকারে Child.prototype = Object.create(Parent.prototype), আর null-prototype dictionary. Class আসার পরে প্রথমটি কমেছে, দ্বিতীয়টি নিরাপত্তার কারণে আরও গুরুত্বপূর্ণ হয়েছে।

মূল উপলব্ধি: একটি ভাষায় একটি মৌলিক কাজের সরাসরি পথ না থাকলে community কৌশল বানায়, আর সবচেয়ে ভালো কৌশলটি পরে মানদণ্ডে ঢোকে। Object.create হলো JavaScript-এর আসল prototype মডেলকে “Java-র চেহারা” ছাড়া সরাসরি ব্যবহারের দরজা।

প্র ০৪__proto__ প্রায় সব browser-এ বহু বছর ধরে চলেছে, তবুও ES2015 একে মূল মানদণ্ডে না রেখে “Annex B — legacy”-তে রাখল। কেন, আর এর সাথে prototype pollution-এর সম্পর্ক কী?

__proto__-এর জন্ম Netscape-এর JavaScript engine-এ, পরে Mozilla-র SpiderMonkey-তে। নামের দুই পাশের দুটি underscore তখনকার রীতিতে বোঝাত “এটি ভেতরের, অ-মানক জিনিস”। কিন্তু ডেভেলপাররা এটি ব্যবহার করতে শুরু করলেন, কারণ prototype পড়া বা বদলানোর আর কোনো সহজ উপায় ছিল না। Safari আর Chrome-এর engine-ও ওয়েবের সাইট চালু রাখতে এটি নকল করল। Internet Explorer এটি সমর্থন করেছে অনেক পরে, IE11-এ।

মানদণ্ড-কমিটির সামনে উভয়সংকট। একদিকে __proto__-এর নকশা খারাপ: এটি একটি সাধারণ property-র নামের মতো দেখায়, অথচ আসলে Object.prototype-এ বসানো একটি accessor — তাই ডেটার key আর ভাষার যন্ত্র একই নামে মিশে যায়। Null-prototype object-এ এটি থাকেই না। অন্যদিকে লক্ষ লক্ষ পাতা এর ওপর নির্ভর করছিল — বাদ দিলে ওয়েব ভাঙবে। সমাধান: ES5 নিয়ে এল পরিষ্কার Object.getPrototypeOf, আর ES2015 আনল Object.setPrototypeOf. সাথে __proto__-কে ES2015-এ লিখিত মানদণ্ডে আনা হলো Annex B-তে — specification-এর সেই অংশে, যা মূলত browser-এর জন্য, আর যার ভূমিকায় বলা হয় এগুলো পুরোনো সামঞ্জস্যের জন্য; নতুন কোডে এগুলো ব্যবহার না করাই কাম্য।

নিরাপত্তার দিকটি পরে বড় হয়ে দেখা দেয়। কোনো লাইব্রেরি যদি বাইরের JSON থেকে আসা key ধরে ধরে object-এ মান বসায় (deep merge, “path দিয়ে মান বসাও” ধরনের function), আর key হয় "__proto__", তাহলে লেখা চলে যায় Object.prototype-এ — অর্থাৎ পুরো প্রোগ্রামের সব object-এ একটি নতুন inherited property ঢুকে যায়। একে বলে prototype pollution; বিভিন্ন জনপ্রিয় JavaScript লাইব্রেরিতে ২০১৮–২০১৯ সালের দিকে এমন ত্রুটি পাওয়া ও সংশোধন করা হয়েছে। প্রতিরক্ষা: বাইরের key দিয়ে লিখতে Map বা Object.create(null), "__proto__", "constructor", "prototype" key বাদ দেওয়া, আর Object.hasOwn দিয়ে যাচাই। Node.js-এ --disable-proto নামে একটি option আছে, যা দিয়ে এই accessor পুরো প্রোগ্রাম থেকে সরিয়ে দেওয়া যায়, অথবা ব্যবহার করলেই error দেওয়ানো যায়।

লক্ষ করুন, JSON.parse('{"__proto__": 1}') নিজে নিরাপদ — এটি সাধারণ own property বানায়, setter চালায় না। বিপদ আসে পরে, যখন সেই object-এর key ধরে অন্য object-এ target[key] = … লেখা হয়।

মূল উপলব্ধি: ভাষার যন্ত্র আর ব্যবহারকারীর ডেটা একই নামের জগতে থাকলে নিরাপত্তা-ত্রুটি অবধারিত। Annex B-র __proto__ হলো সেই ভুলের স্মারক — চালু রাখা হয়েছে শুধু অতীতের জন্য।

প্র ০৫২০১৮ সালের “SmooshGate”-এ একটি পুরোনো লাইব্রেরির জন্য পুরো ভাষাকে একটি method-এর নাম বদলাতে হলো। Built-in prototype বদলানো কীভাবে “ওয়েব ভাঙে”, আর polyfill কেন আলাদা?

২০০৫–২০১০ সালের দিকে Prototype.js আর MooTools-এর মতো জনপ্রিয় লাইব্রেরি built-in prototype বাড়ানোকে নিজেদের দর্শন বানিয়েছিল: Array.prototype, String.prototype, Function.prototype-এ নিজের সুবিধাজনক method বসানো। তখন ভাষায় forEach, map-এর মতো method সব browser-এ ছিল না, তাই এটি সুবিধাজনক ছিল। সমস্যা হলো, সেই লাইব্রেরি ব্যবহার করা অসংখ্য সাইট আজও চালু, আর কেউ সেগুলো আপডেট করবে না।

২০১৮ সালে TC39 Array.prototype.flatten যোগ করার পথে ছিল, আর Firefox-এর পরীক্ষামূলক সংস্করণে এটি চালু হতেই কিছু সাইট ভাঙতে শুরু করল। কারণ MooTools-এর নিজের একটি flatten ছিল, যার আচরণ আলাদা। MooTools নিজের method বসাত শুধু “আগে থেকে না থাকলে”; আর নিজের কিছু collection-এ array-র method কপি করত enumeration দিয়ে। Native flatten থাকায় MooTools নিজেরটি বসাল না, আর native method non-enumerable বলে কপিও হলো না — ফলে MooTools-নির্ভর পাতার কোড একটি না-থাকা method ডাকল। প্রস্তাবের লেখক মজা করে প্রস্তাব দিলেন নাম হোক smoosh — internet-এ হইচই পড়ল, ঘটনার নাম হলো “SmooshGate”. শেষে নাম রাখা হলো flat. এর আগে একই কারণে Array.prototype.contains-এর নাম হয়েছিল includes (ES2016).

শিক্ষাটি মৌলিক: built-in prototype হলো পুরো ওয়েবের ভাগের জায়গা। আপনি সেখানে একটি নাম দখল করলে সেই নাম শুধু আপনার কোডের নয় — পুরো ভাষার ভবিষ্যতের ওপর বোঝা হয়ে যায়। কারণ browser-এর অলিখিত নীতি “ওয়েব ভাঙবেন না”: পুরোনো সাইট ভাঙলে দোষ নতুন বৈশিষ্ট্যের।

Polyfill একই যন্ত্র ব্যবহার করে, কিন্তু উল্টো দিকে। Polyfill নতুন নাম আবিষ্কার করে না; মানদণ্ডে ইতিমধ্যে ঠিক হওয়া নাম আর আচরণই পুরোনো browser-এ যোগ করে, আর শুধু তখনই যখন native সংস্করণ নেই। ফলে browser যখন native সংস্করণ আনে, কোনো সংঘর্ষ হয় না — দুটির আচরণ এক। এজন্য polyfill অবশ্যই মানক আচরণ হুবহু মানবে, non-enumerable হবে, আর কোনো প্রস্তাবিত (এখনো চূড়ান্ত নয়) বৈশিষ্ট্যের polyfill সতর্কতার সাথে ব্যবহার করতে হবে — প্রস্তাব বদলে গেলে সেটিও আরেকটি MooTools হয়ে যেতে পারে।

মূল উপলব্ধি: Prototype chain-এর “এক জায়গায় বদল, সবখানে প্রভাব” শক্তিটিই built-in বদলানোকে বিপজ্জনক করে। নিজের helper সাধারণ function হিসেবে রাখুন; built-in prototype-এ হাত দেওয়ার অধিকার শুধু মানক আচরণের polyfill-এর।

অনুশীলন

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

  1. শিকল আঁকুন: function Book(title) { this.title = title; } আর const b = new Book("পদ্মা নদীর মাঝি"); — b থেকে null পর্যন্ত শিকলটি লিখুন। তারপর Book function-এর নিজের শিকলটিও লিখুন। b.prototype কী দেবে?

    b → Book.prototype → Object.prototype → null. Book নিজে একটি function, তাই তার শিকল Book → Function.prototype → Object.prototype → null. b.prototype হলো undefined — prototype property শুধু function-এর থাকে; b একটি সাধারণ object, তার prototype পড়তে Object.getPrototypeOf(b) লাগে। এই প্রশ্নটিই [[Prototype]] বনাম prototype-এর পুরো পার্থক্য পরীক্ষা করে।

  2. আউটপুট অনুমান করুন: const base = { qty: 1 }; const a = Object.create(base); const c = Object.create(base); a.qty = 5; base.qty = 9; — এখন a.qty, c.qty, Object.hasOwn(c, "qty") কী? তারপর delete a.qty করলে a.qty কত?

    a.qty হলো ৫ — a.qty = 5 লেখায় a-এর নিজের property তৈরি হয়েছিল (shadowing), তাই base-এর বদল সে দেখে না। c.qty হলো ৯ — c-এর নিজের কিছু নেই, সে live link ধরে base-এর বর্তমান মান পড়ে (কপি নয়!)। Object.hasOwn(c, "qty") হলো false. delete a.qty-এর পরে আড়াল সরে যায়, তাই a.qty হয় ৯.

  3. বাগ খুঁজুন: একজন লিখেছেন function Order(id) { this.id = id; } Order.prototype = { total() { return 0; } }; const o = new Order(1); — তারপর o.constructor === Order দেখাচ্ছে false. আরেক জায়গায় তিনি লিখেছেন const x = Order(2); আর x.id পড়তে গিয়ে TypeError পাচ্ছেন। দুটির কারণ বলুন।

    প্রথমটিতে Order.prototype-কে একটি নতুন object literal দিয়ে পুরো বদলে দেওয়া হয়েছে; সেই object-এ constructor নেই, তাই o.constructor শিকল ধরে Object.prototype.constructor, অর্থাৎ Object পায়। সমাধান: literal-এ constructor: Order যোগ করা (ভালো হয় Object.defineProperty দিয়ে non-enumerable), অথবা prototype বদলে না দিয়ে method আলাদা করে যোগ করা, অথবা class. দ্বিতীয়টিতে new নেই — Order(2) সাধারণ call, কিছু return করে না, তাই x হলো undefined, আর undefined.id পড়তে TypeError. (সাথে non-strict script-এ window.id-ও বদলে গেছে।)

  4. ছোট কোড লিখুন: countTags(tags) লিখুন, যা একটি tag-এর array (যেমন ["নতুন", "ছাড়", "নতুন", "constructor"]) নিয়ে প্রতিটি tag কতবার এসেছে তা গুনে একটি object ফেরত দেবে — এমনভাবে, যাতে "constructor" বা "__proto__"-এর মতো tag-ও ঠিকঠাক গোনা হয়। তারপর ফলটিতে "toString" in result কী দেয় দেখুন।

    একটি সমাধান: function countTags(tags) { const result = Object.create(null); for (const t of tags) result[t] = (result[t] ?? 0) + 1; return result; }. Null-prototype object-এ কোনো inherited key নেই, তাই result["constructor"] শুরুতে undefined, ?? 0 কাজ করে, আর "__proto__"-ও সাধারণ key হিসেবে বসে। "toString" in result হলো false. সমান ভালো বিকল্প: Map দিয়ে গোনা (m.set(t, (m.get(t) ?? 0) + 1)). মনে রাখবেন, ফলাফলে hasOwnProperty method নেই — যাচাইয়ে Object.hasOwn.

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

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

ক্রমJavaScript-এ new Fn(args) চললে কাজগুলো কোন ক্রমে ঘটে, সাজান (▲▼ চেপে বা টেনে)।

  1. 🆕 একটি নতুন খালি object তৈরি হয়
  2. 🔗 তার [[Prototype]]-এ Fn.prototype বসানো হয়
  3. ▶️ this = নতুন object ধরে Fn-এর শরীর চলে
  4. ↩️ Fn কোনো object return না করলে নতুন object-টিই ফল হয়

function Product(n) { this.n = n; } আর const p = new Product("চাল"); থাকলে JavaScript-এ কোনটি true?

  • p.prototype === Product.prototype
  • Object.getPrototypeOf(Product) === Product.prototype
  • Object.getPrototypeOf(p) === Product.prototype
  • Object.hasOwn(Product.prototype, "n")

const base = { greet() { return "hi"; } }; const u = Object.create(base); u.greet = () => "bye"; — এরপর base.greet() আর u.greet() কী দেয়?

  • "bye" আর "bye"
  • "hi" আর "bye"
  • "hi" আর "hi"
  • TypeError, কারণ greet inherited

JavaScript-এ const d = Object.create(null); — নিচের কোনটি সত্য?

  • d.toString() "[object Object]" দেয়
  • d.hasOwnProperty("x") নিরাপদে false দেয়
  • d.__proto__ হলো Object.prototype
  • "constructor" in d হলো false

JavaScript-এ obj instanceof Fn আসলে কী যাচাই করে (Fn-এ Symbol.hasInstance বদলানো নেই ধরে)?

  • Fn.prototype obj-এর prototype chain-এর কোথাও আছে কি না
  • obj আসলেই Fn দিয়ে new করে তৈরি হয়েছিল কি না
  • obj.constructor === Fn কি না
  • Object.getPrototypeOf(obj) === Fn.prototype কি না, শুধু এক ধাপ

একটি constructor-এ this.priceWithVat = function () { … } লেখার বদলে Product.prototype.priceWithVat = function () { … } লেখার প্রধান সুবিধা কী?

  • this সবসময় ঠিক থাকে
  • method গোপন হয়ে যায়
  • সব object একটিই function ভাগ করে, তাই মেমরি বাঁচে
  • new ছাড়া ডাকলেও কাজ করে

JavaScript-এ Array.prototype-এ নিজের method (যেমন last) যোগ করাকে কেন খারাপ চর্চা ধরা হয়?

  • কারণ এটি করলে সাথে সাথে TypeError আসে
  • একই নামের ভবিষ্যৎ মানক method বা অন্য লাইব্রেরির সাথে সংঘর্ষ হতে পারে
  • কারণ শুধু নতুন তৈরি array method-টি পায়
  • কারণ ভাষা Array.prototype-এ কিছু যোগ করা নিষিদ্ধ করেছে

শব্দকোষ

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

মনে রাখুন

  1. প্রতিটি object-এর একটি লুকানো [[Prototype]] link; property নিজের না পেলে engine শিকল ধরে খোঁজে — Object.prototype → null-এ শেষ, না পেলে undefined.
  2. Fn.prototype শুধু function-এর property — new Fn()-এ তৈরি object-এর prototype. পড়তে Object.getPrototypeOf; __proto__ শুধু পুরোনো কোডে।
  3. পড়া শিকল ধরে ওপরে যায়, লেখা নিজের ঘরে (shadowing) — শিকলে setter থাকলে সেটি চলে। for…in inherited enumerable key দেখায়, Object.keys দেখায় না।
  4. new = নতুন object → prototype যুক্ত → this ধরে চালানো → ফেরত। Method prototype-এ একবার; উত্তরাধিকারে Object.create(Parent.prototype) + Parent.call(this, …).
  5. Dictionary-র জন্য Object.create(null) বা Map; built-in prototype-এ হাত শুধু polyfill-এর; prototype ঠিক করুন তৈরির সময়ে, পরে বদলালে engine ধীর হয়।

পরের পর্বে: এই পর্বের constructor function, Product.prototype.priceWithVat, Object.create(Parent.prototype) আর Parent.call(this) — সব মিলে যে লম্বা পদ্ধতি, পর্ব ২৬-এ তা হয়ে যাবে কয়েক লাইনের class: extends, super, getter/setter, static আর সত্যিকারের গোপন #private field — আর আপনি জানবেন ভেতরে আসলে কী ঘটছে।

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

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