এই পর্বে যা শিখবেন
- Object literal দিয়ে একটি গ্রাহক বা অর্ডারের ছবি আঁকা, আর dot ও bracket দিয়ে property পড়া, যোগ, বদল ও মোছা
- Method, method shorthand আর method-এর ভেতরে
thisকাকে দেখায় (মূল নিয়ম) - Property আছে কি না যাচাই (
in,Object.hasOwn), সব property ঘুরে দেখা, আর key কোন ক্রমে আসে - Reference কী — কেন একটি variable বদলালে অন্যটিও বদলায়, আর shallow বনাম deep কপি
- Object রক্ষা ও নিয়ন্ত্রণ —
freeze,seal, property descriptor, getter/setter — আরObject.groupByদিয়ে অর্ডার ভাগ করা
১ · Object কী — নাম দেওয়া ঘর দিয়ে একটি জিনিসের ছবি
মডিউল ২-এ আপনি একটি একটি মান নিয়ে কাজ করেছেন — একটি দাম, একটি নাম, একটি true. কিন্তু বাস্তবের কোনো জিনিস একটিমাত্র মান দিয়ে বোঝানো যায় না। একটি অনলাইন দোকানের একজন গ্রাহকের কথা ভাবুন: তাঁর নাম আছে, ফোন নম্বর আছে, ঠিকানা আছে, তিনি এ পর্যন্ত কয়টি অর্ডার দিয়েছেন সেই সংখ্যা আছে। এগুলো আলাদা আলাদা variable-এ রাখলে — customerName, customerPhone, customerDistrict — দশজন গ্রাহকের জন্য চল্লিশটি variable লাগবে, আর কোন ফোন কার, তা মেলানো কঠিন হবে।
সমাধান হলো সব তথ্য একটি ব্যাগে ভরে ব্যাগটির একটি নাম দেওয়া। JavaScript-এ এই ব্যাগের নাম object. Object-এর ভেতরে প্রতিটি তথ্য একটি নাম (key) আর একটি মান (value) নিয়ে থাকে। এই জোড়াকে বলে property. সবচেয়ে সহজে object বানানো যায় বাঁকা বন্ধনী { } দিয়ে — একে বলে object literal:
const customer = {
name: "Riya Akter", // key: name, value: একটি string
phone: "01711-000000",
orders: 3, // value যেকোনো টাইপ হতে পারে
verified: true,
address: { // object-এর ভেতরে আরেকটি object
district: "Khulna",
area: "Sonadanga",
},
};প্রতিটি লাইন একটি property: বাঁ দিকে key, কোলন, তারপর value, আর property-গুলোর মাঝে কমা। শেষ property-র পরের কমাটি (trailing comma) বৈধ — নতুন লাইন যোগ করলে diff পরিষ্কার থাকে। Value হিসেবে string, number, boolean, আরেকটি object, array, এমনকি function-ও রাখা যায়। address একটি nested object — বাস্তব ডেটা প্রায় সবসময় এমন স্তরে স্তরে সাজানো থাকে।
স্কুলের ভর্তি ফরমের কথা ভাবুন। ফরমে ছাপা থাকে “নাম:”, “পিতার নাম:”, “জেলা:” — এগুলো key. আপনি কলমে যা লেখেন — “রিয়া আক্তার”, “খুলনা” — সেগুলো value. প্রতিটি ছাত্রের জন্য একটি করে ফরম, কিন্তু ঘরের নামগুলো একই। একটি object হলো ঠিক এমন একটি পূরণ করা ফরম। আর ফরমের ভেতরে “স্থায়ী ঠিকানা” অংশে আলাদা ছোট ঘর থাকলে, সেটিই nested object.
দুটি বড় ধারণা — শুরুতেই মনে রাখুন
🗂️ Key সবসময় string (বা Symbol)
{ 1: "এক" } লিখলেও key আসলে "1" — string. Object আসলে “নাম → মান” তালিকা; নাম হিসেবে শুধু string আর Symbol চলে। যেকোনো টাইপের key দরকার হলে Map (পর্ব ১৮)।
🔗 Object একটি reference মান
Number বা string-এর মতো primitive মান variable-এর ঘরে সরাসরি বসে। Object বসে heap-এ; variable-এ থাকে শুধু তার ঠিকানা। এই একটি তথ্য না জানলে অর্ধেক বাগ অদৃশ্য থাকে — অংশ ৫-এ পুরোটা।
⚙️ Method = function মানের property
{ total() { … } } — object নিজের ডেটার ওপর কাজও জানে। ডেটা আর আচরণ একসাথে থাকে, যা পরে class (পর্ব ২৬)-এর ভিত্তি।
JavaScript-এ প্রায় সবকিছুই শেষ পর্যন্ত object: array একটি বিশেষ object (পর্ব ১৫), function একটি object, তারিখ একটি object, ব্রাউজারের পাতাটিও object-এর গাছ (DOM, পর্ব ৩৭)। আর সার্ভার থেকে আসা JSON ডেটা (পর্ব ১৯) দেখতে হুবহু object literal-এর মতো। তাই এই পর্বটি পুরো মডিউল ৩-এর ভিত্তি।
Python-এর dict-এর সাথে মিল সবচেয়ে বেশি — key থেকে value, ঢোকানোর ক্রম মনে রাখে — কিন্তু JavaScript object-এর key শুধু string বা Symbol, আর obj.key ও obj["key"] দুটোই চলে (Python-এর dict). Java বা C++-এ object বানাতে আগে class লাগে আর field-এর তালিকা স্থির; JavaScript-এ class ছাড়াই { } দিয়ে object, আর চলার সময় যেকোনো property যোগ বা মোছা যায় (Java-র class ও object). Java-র মতোই variable-এ থাকে reference — b = a কপি করে না। এগুলোর কোনোটি না জানলে এই বাক্সটি বাদ দিন।
২ · Property পড়া, লেখা আর মোছা — dot বনাম bracket
Property পড়ার দুটি উপায়। প্রথমটি dot notation — object-এর নাম, একটি ডট, তারপর key: customer.name. দ্বিতীয়টি bracket notation — তৃতীয় বন্ধনীর ভেতরে key একটি string হিসেবে: customer["name"]. দুটোই একই মান দেয়। তাহলে দুটো কেন?
customer.name; // "Riya Akter"
customer["name"]; // একই
customer.address.district; // "Khulna" — ডট পরপর চলে
const field = "phone";
customer[field]; // "01711-000000" — key variable-এ
customer.field; // undefined — "field" নামের key খোঁজে!
const product = { "পণ্যের নাম": "মিনিকেট চাল", "unit-price": 80 };
product["পণ্যের নাম"]; // space আছে → bracket ছাড়া উপায় নেই
product["unit-price"]; // product.unit-price হতো product.unit − priceDot-এর পরে যা লেখেন, তা হুবহু key-এর নাম হিসেবে পড়া হয় — এটি একটি identifier হতে হবে (অক্ষর, অঙ্ক, _, $; অঙ্ক দিয়ে শুরু নয়, space বা হাইফেন নয়)। Bracket-এর ভেতরে থাকে একটি expression; আগে তার মান হিসাব হয়, তারপর সেই মান (string-এ বদলে) key হিসেবে খোঁজা হয়। তাই customer[field] খোঁজে "phone", কিন্তু customer.field খোঁজে আক্ষরিক "field" — নতুনদের খুব সাধারণ ভুল। বাংলা অক্ষর identifier-এ বৈধ, তাই product.দাম কাজ করে; কিন্তু space থাকলে bracket বাধ্যতামূলক।
Key আগে থেকে জানা আর সাধারণ নাম হলে dot — পড়তে সহজ। Key চলার সময় ঠিক হয় (variable, লুপ, user-এর পছন্দ), অথবা নামে space, হাইফেন বা অঙ্ক দিয়ে শুরু — তখন bracket.
যোগ, বদল আর মোছা
Java-র মতো ভাষায় object-এর field আগে থেকে ঘোষণা করতে হয়। JavaScript-এ object খোলা খাতা — যেকোনো সময় নতুন property লেখা যায়:
customer.email = "riya@example.com"; // ছিল না → নতুন property যোগ
customer.orders = 4; // ছিল → মান বদল
customer.orders += 1; // 5
delete customer.verified; // property পুরোপুরি মুছে ফেলা
customer.verified; // undefined (আর নেই)
customer.nickname; // undefined (কখনো ছিলই না)খেয়াল করুন, customer একটি const — তবুও ভেতরে বদলানো গেল। পর্ব ০৭-এ দেখেছেন, const শুধু বলে “এই নামটি অন্য object-এ সরানো যাবে না” (customer = {} হলে TypeError). ভেতরের property বদলানো একটি mutation, আর const তা আটকায় না। আটকাতে চাইলে Object.freeze (অংশ ৭)।
মুছে ফেলা আর undefined বসানো এক নয়। delete obj.x করলে key-টিই চলে যায়; obj.x = undefined করলে key থাকে, শুধু মান undefined. পড়তে গেলে দুটোই undefined দেয়, কিন্তু Object.keys, in আর JSON.stringify-এ পার্থক্য দেখা যায়। আরও একটি কথা: engine-এর দিক থেকে delete ব্যয়বহুল হতে পারে — পর্ব ০৫-এর hidden class ভেঙে object ধীর “dictionary mode”-এ চলে যেতে পারে। গরম লুপে বারবার delete না করে নতুন object বানানো ভালো।
ES2015-এর দুটি সুবিধা — shorthand আর computed key
প্রায়ই variable-এর নাম আর key-এর নাম একই হয়: { name: name, phone: phone }. ES2015 (ES6) থেকে লিখতে পারেন শুধু { name, phone } — একে বলে property shorthand. আর key-টি নিজেই যদি হিসাব করে বের করতে হয়, object literal-এর ভেতরেই bracket ব্যবহার করুন — computed property name:
const name = "Riya", phone = "01711-000000";
const c = { name, phone }; // { name: "Riya", phone: "01711-000000" }
const lang = "bn";
const labels = {
[`title_${lang}`]: "অর্ডারের তালিকা", // key হবে "title_bn"
[1 + 1]: "দুই", // key হবে "2"
};Computed key বিশেষ কাজে আসে যখন একটি function একটি ফর্মের ঘর আপডেট করে: function setField(form, key, value) { return { ...form, [key]: value }; } — কোন ঘর বদলাবে, তা চলার সময় ঠিক হয়। এখানে ...form হলো spread, যা অংশ ৬-এ দেখবেন।
৩ · Method আর this — object নিজের ডেটা নিয়ে কাজ করে
পর্ব ১৩-এ দেখেছেন, function একটি মান — তাই তাকে object-এর property হিসেবেও রাখা যায়। এমন property-কে বলে method. নতুন কোনো জিনিস নয়; শুধু এমন একটি property, যার মান একটি function. লেখার তিনটি রূপ:
const cart = {
owner: "Riya",
items: [{ price: 80, qty: 5 }, { price: 120, qty: 2 }],
total() { // ✅ method shorthand (ES2015)
let sum = 0;
for (const it of this.items) sum += it.price * it.qty;
return sum;
},
greet: function () { // পুরোনো রূপ — একই কাজ করে
return "স্বাগতম, " + this.owner;
},
badGreet: () => "স্বাগতম, " + this.owner, // ❌ arrow: this ভুল
};
cart.total(); // 640
cart.greet(); // "স্বাগতম, Riya"
cart.badGreet(); // "স্বাগতম, undefined"Method shorthand total() { … } হলো total: function () { … }-এর ছোট রূপ — আধুনিক কোডে এটিই নিয়ম। ভেতরের this.items লক্ষ করুন: method নিজের object-এর ডেটা পড়তে this ব্যবহার করে। badGreet একটি arrow function; পর্ব ১৩-এ দেখেছেন, arrow-এর নিজের this নেই, সে বাইরের scope-এর this নেয়। Object literal-এর বাঁকা বন্ধনী কোনো scope তৈরি করে না, তাই সেই this object নয়।
this-এর মূল নিয়ম — কে ডাকছে, ডটের বাঁ পাশে কে
সাধারণ function বা method-এর ভেতরে this কাকে দেখাবে, তা লেখার সময় ঠিক হয় না — ঠিক হয় ডাকার সময়. সবচেয়ে সাধারণ নিয়ম: obj.method() আকারে ডাকলে this হলো ডটের বাঁ পাশের object. তাই একই function দুটি object-এ বসালে দুই জায়গায় দুই রকম this পাবে।
এর একটি উল্টো ফাঁদ আছে। Method-কে object থেকে আলাদা করে একটি variable-এ নিলে, বা callback হিসেবে পাঠালে, ডটের বাঁ পাশে আর কেউ থাকে না:
const greet = cart.greet; // শুধু function-টি নেওয়া হলো, object নয়
greet(); // this আর cart নয় → owner পাওয়া যায় না
setTimeout(cart.greet, 100); // একই সমস্যা — timer সাধারণ function হিসেবে ডাকেStrict mode-এ (module ও class-এর ভেতরে স্বয়ংক্রিয়) greet()-এর ভেতরে this হয় undefined, আর this.owner পড়তে গেলে TypeError আসে। সাধারণ script-এ this হয় global object, তাই চুপচাপ ভুল মান আসে। setTimeout-এর বেলায় ব্রাউজার this হিসেবে সবসময় global object window দেয় — তাতেও owner নেই। সমাধান — setTimeout(() => cart.greet(), 100) বা bind. this-এর পুরো চারটি নিয়ম, call, apply আর bind আসবে পর্ব ২৪-এ। এই পর্বের জন্য একটি বাক্য যথেষ্ট:
obj.method() দিয়ে · method-কে আলাদা করে পাঠালে this হারায়৪ · Property আছে কি না, সব property ঘোরা, আর key-এর ক্রম
“এই গ্রাহকের কি email আছে?” — প্রশ্নটি সহজ মনে হয়, কিন্তু উত্তরের আগে জানতে হবে JavaScript একটি property কীভাবে খোঁজে। প্রতিটি সাধারণ object-এর পেছনে লুকানো একটি সংযোগ থাকে আরেকটি object-এর দিকে, যার নাম prototype. নিজের ভেতরে key না পেলে engine সেই prototype-এ খোঁজে, তারপর তারও prototype-এ — এভাবে একটি শিকল ধরে। এই শিকলকে বলে prototype chain. পুরো গল্প পর্ব ২৫-এ; এখানে শুধু খোঁজার ক্রমটি দেখুন।
- cust (নিজের property)
name: "Riya"own property - Object.prototype
toString, hasOwnProperty…সবার ভাগের method - null
শিকলের শেষআর কোথাও নেই
const cust = { name: "Riya" };
// cust-এর prototype → Object.prototype → null
cust.name → নিজের ভেতরে "name" আছে → "Riya"
cust-এ "toString" নেই
Object.prototype-এ আছে → সেই function পাওয়া গেল
cust-এ নেই → Object.prototype-এ নেই → null
ফল: undefined (কোনো error নয়)
"toString" in cust → true // শিকল ধরে খোঁজে Object.hasOwn(cust, "toString") → false // শুধু নিজের Object.hasOwn(cust, "name") → true
- লুকানো সংযোগ।
{ name: "Riya" }বানালে object-এ একটি property থাকে —name. কিন্তু পেছনে একটি লুকানো link আছেObject.prototype-এর দিকে, যেখানেtoString,hasOwnProperty-এর মতো সবার ভাগের method রাখা। তার পরেnull— শিকলের শেষ। - নিজের property আগে।
cust.nameপড়লে engine প্রথমে object-এর নিজের ঘরগুলো দেখে।nameপাওয়া গেল, খোঁজা শেষ। এমন ঘরকে বলে own property. - না পেলে prototype-এ।
cust.toString— নিজের ঘরে নেই, তাই engine এক ধাপ ওপরেObject.prototype-এ যায় এবং সেখানে পায়। এজন্যই আপনি কখনো না লিখলেও প্রতিটি object-এtoString()চলে। এটি উত্তরাধিকার (inheritance). - শিকলের শেষে
undefined.cust.phone— নিজের ঘরে নেই, prototype-এ নেই, তারপরnull. Engine হাল ছেড়ে দেয় আর ফেরত দেয়undefined— error নয়। তাই ভুল বানানের key (cust.nmae) চুপচাপundefinedদেয়, যা খুঁজে পাওয়া কঠিন বাগ। - যাচাইয়ের দুই প্রশ্ন। “শিকলের কোথাও আছে?” — উত্তর দেয়
in. “এই object-এর নিজের আছে?” — উত্তর দেয়Object.hasOwn. গ্রাহকের ডেটা যাচাইয়ে প্রায় সবসময় দ্বিতীয় প্রশ্নটিই আসল প্রশ্ন।
আছে কি না যাচাইয়ের চারটি উপায়
| উপায় | কী দেখে | কখন |
|---|---|---|
obj.x !== undefined | মান undefined কি না | দ্রুত, কিন্তু key আছে অথচ মান undefined হলে ভুল বলে |
"x" in obj (in) | নিজের বা prototype chain-এর কোথাও | method বা inherited কিছু আছে কি না জানতে |
Object.hasOwn(obj, "x") | শুধু নিজের property | ডেটা যাচাইয়ে — আধুনিক পছন্দ (ES2022) |
obj.hasOwnProperty("x") | শুধু নিজের property | পুরোনো কোডে; কিছু object-এ ভেঙে যায় (নিচে দেখুন) |
Object.hasOwn ES2022-এ যুক্ত হয়েছে পুরোনো hasOwnProperty-এর দুটি সমস্যা দূর করতে। (১) Object.create(null) দিয়ে বানানো object-এর কোনো prototype নেই, তাই তাতে hasOwnProperty method-ই নেই — ডাকলে TypeError. (২) ডেটার ভেতরে কেউ hasOwnProperty নামেই একটি key পাঠালে (যেমন user-এর form থেকে), সেই key আসল method-কে ঢেকে দেয়। Object.hasOwn(obj, key) object-এর ওপর নির্ভর করে না, তাই দুই ক্ষেত্রেই ঠিক কাজ করে।
আর গভীরের property পড়ার সময় মাঝপথে কিছু undefined হতে পারে — তখন পর্ব ০৮-এর optional chaining ?. আর ?? আপনার বন্ধু: customer.address?.district ?? "জেলা জানা নেই". address না থাকলে TypeError-এর বদলে undefined, তারপর ?? একটি ডিফল্ট বসায়।
সব property ঘুরে দেখা
একটি object-এর সব key বা মান নিয়ে কাজ করতে তিনটি static method আছে। তিনটিই শুধু নিজের আর enumerable (তালিকায় দেখানো যায় এমন) string key দেখে, আর একটি array ফেরত দেয়:
const stock = { rice: 50, dal: 20, oil: 0 };
Object.keys(stock); // [ 'rice', 'dal', 'oil' ]
Object.values(stock); // [ 50, 20, 0 ]
Object.entries(stock); // [ [ 'rice', 50 ], [ 'dal', 20 ], [ 'oil', 0 ] ]
for (const [item, qty] of Object.entries(stock)) {
if (qty === 0) console.log(item + " শেষ — নতুন অর্ডার দিন");
}
const prices = Object.fromEntries([["rice", 80], ["dal", 120]]);
// { rice: 80, dal: 120 } — entries থেকে আবার objectObject.keys এসেছে ES5-এ, values আর Object.entries ES2017-এ, আর তার উল্টো Object.fromEntries ES2019-এ। entries প্রতিটি property-কে [key, value] জোড়া বানায়; for…of (পর্ব ১২)-এর সাথে [item, qty] লিখে জোড়াটি দুই ভাগ করা যায় — এই destructuring পর্ব ১৭-এ বিস্তারিত। “entries বানাও → array method দিয়ে বদলাও → fromEntries দিয়ে ফেরত object” — এটি খুব প্রচলিত একটি কৌশল, যেমন সব দাম ১০% বাড়ানো।
পর্ব ১২-এ for…in দেখেছিলেন। এটিও key ঘোরে, কিন্তু একটি বড় পার্থক্য: for…in prototype chain-এর enumerable property-গুলোও দেখায়. সাধারণ object literal-এ এর প্রভাব চোখে পড়ে না, কারণ Object.prototype-এর method-গুলো enumerable নয়। কিন্তু কোনো পুরোনো লাইব্রেরি Object.prototype-এ কিছু যোগ করলে, বা object অন্য object থেকে উত্তরাধিকার পেলে, for…in সেগুলোও তুলে আনে। তাই পুরোনো কোডে প্রায়ই দেখবেন for (k in obj) { if (Object.hasOwn(obj, k)) … }. নতুন কোডে সহজ নিয়ম: object ঘুরতে Object.keys/entries + for…of.
Key কোন ক্রমে আসে?
অনেকে ভাবেন object-এর key-এর কোনো নির্দিষ্ট ক্রম নেই। আধুনিক JavaScript-এ আছে, আর নিয়মটি একটু অদ্ভুত: প্রথমে integer-এর মতো দেখতে key-গুলো ছোট থেকে বড় ক্রমে, তারপর বাকি string key-গুলো যে ক্রমে যোগ হয়েছিল সেই ক্রমে (তারপর Symbol key, যা Object.keys দেখায় না)। ধাপে ধাপে দেখুন:
1const codes = {};2codes.b = 1;3codes[10] = "x";4codes.a = 2;5codes[2] = "y";6codes["-1"] = "z";7console.log(Object.keys(codes));
| যে ক্রমে যোগ হলো | |
| Object.keys |
- খালি object. কোনো key নেই,
Object.keysদেয়[]. - প্রথম key “b”. সাধারণ string key — তালিকায়
'b'. codes[10]— সংখ্যা দিলেও key string. Key হলো"10". কিন্তু এটি integer-এর মতো দেখতে (array index-এর মতো), তাই পরে যোগ হলেও তালিকায়'b'-এর আগে চলে গেল।- “a” যোগ হলো। সাধারণ string key, তাই যোগের ক্রমে —
'b'-এর পরে। বর্ণমালার ক্রম নয়! codes[2]. আরেকটি integer-এর মতো key. Integer key-গুলো নিজেদের মধ্যে ছোট থেকে বড় ক্রমে সাজে, তাই'2'এল'10'-এর আগে, সবার শুরুতে।"-1"— integer নয়! ঋণাত্মক সংখ্যা array index হতে পারে না, তাই engine একে সাধারণ string ধরে। সে বসল একদম শেষে, যোগের ক্রমে।"01"বা"1.5"-ও একই কারণে সাধারণ string.- ফল।
[ '2', '10', 'b', 'a', '-1' ]. শিক্ষা: জেলা কোড, রোল নম্বর বা পণ্যের ID-কে key বানালে সেগুলো নিজে থেকে সংখ্যার ক্রমে সাজবে, আপনার যোগের ক্রমে নয়। ক্রম গুরুত্বপূর্ণ হলে array বা Map (পর্ব ১৮) ব্যবহার করুন — Map সব key-কে ঢোকানোর ক্রমেই রাখে।
৫ · Reference — variable-এ object থাকে না, থাকে তার ঠিকানা
এবার এই পর্বের সবচেয়ে গুরুত্বপূর্ণ অংশ। পর্ব ০৫-এ দেখেছেন, engine-এর মেমরিতে দুটি এলাকা — stack আর heap. let n = 5 লিখলে ৫ মানটি সরাসরি variable-এর ঘরে বসে। কিন্তু const a = { qty: 1 } লিখলে object-টি তৈরি হয় heap-এ, আর a-এর ঘরে বসে শুধু একটি reference — “object-টি heap-এর ওই জায়গায় আছে”, এই তথ্য। তাহলে const b = a লিখলে কী কপি হয়? Object নয় — শুধু ঠিকানা। ধাপে ধাপে দেখুন:
1const a = { qty: 1 };2const b = a;3b.qty = 5;4console.log(a.qty);5const c = { ...a };6c.qty = 9;7console.log(a.qty, c.qty, a === b, a === c);
| a | |
| b | |
| c | |
| heap #1 | |
| heap #2 |
- Object তৈরি।
{ qty: 1 }heap-এ তৈরি হলো — ধরুন তার ঠিকানা #1.a-এর ঘরে বসল “#1-এ যাও”।aনিজে object নয়, object-এর দিকে একটি তীর। const b = a— শুধু তীর কপি।a-এর ঘরে যা আছে (#1), তা-ইb-এর ঘরে কপি হলো। Heap-এ নতুন কিছু তৈরি হয়নি। এখন দুটি নাম একই object দেখায় — একে বলে aliasing.b.qty = 5.b-এর তীর ধরে #1-এ গিয়েqtyবদলানো হলো। বদলেছে object, কোনো variable নয়।a.qty— ৫!a-ও #1 দেখায়, তাই সে বদলানো মানই পায়। এটি বাগ নয়, এটিই নিয়ম। একটি ব্যাংক অ্যাকাউন্টের দুটি ATM কার্ডের মতো — যে কার্ড দিয়েই টাকা তুলুন, অ্যাকাউন্ট একটিই।{ ...a }— সত্যিকারের নতুন object. Spread একটি নতুন object (#2) তৈরি করে আরa-এর প্রতিটি property তাতে কপি করে।cদেখায় #2-কে। এখন দুটি আলাদা object, শুরুতে একই মান।c.qty = 9. শুধু #2 বদলাল। #1 অক্ষত —a.qtyএখনো ৫.- ফল আর
===. আউটপুট5 9 true false.a === bসত্য, কারণ দুটি একই ঠিকানা।a === cমিথ্যা — ভেতরের মান একসময় এক থাকলেও এরা দুটি আলাদা object. Object-এর===তুলনা করে পরিচয় (identity), বিষয়বস্তু নয়।
Primitive কপি করলে মান কপি হয়; object কপি করলে reference কপি হয়। { a: 1 } === { a: 1 } সবসময় false — দুটি আলাদা জায়গায় তৈরি দুটি object. দুটি object-এর বিষয়বস্তু তুলনা করতে নিজে property ধরে তুলনা করতে হয় (বা কোনো লাইব্রেরির “deep equal”). === এখানে কাজে আসে না।
Function-এ object পাঠালে — কলকারীও বদল দেখে
পর্ব ১৩-এ দেখেছেন, argument parameter-এ কপি হয়ে যায়। Object-এর বেলায় কপি হয় reference — তাই function-এর ভেতরের parameter আর বাইরের variable একই object দেখায়। একে কখনো কখনো pass by sharing বলা হয়:
function applyDiscount(order) {
order.total = order.total * 0.9; // ❌ কলকারীর object-ই বদলে গেল
return order;
}
function withDiscount(order) {
return { ...order, total: order.total * 0.9 }; // ✅ নতুন object
}
function replace(order) {
order = { total: 0 }; // শুধু local নামটি নতুন object দেখাল
} // বাইরের object অক্ষতapplyDiscount ডাকার পরে কলকারীর order-এর মোটও বদলে যায় — যেটি সে হয়তো অন্য জায়গায় দেখাচ্ছিল। দুইবার ডাকলে দুইবার ছাড়! withDiscount একটি নতুন object ফেরত দেয়; মূলটি অক্ষত, তাই এটি একটি pure function (পর্ব ১৩)। শেষ উদাহরণটি উল্টো দিক দেখায়: parameter-কে নতুন object-এ সরালে (reassign) বাইরে কোনো প্রভাব পড়ে না — কারণ শুধু local তীরটি ঘুরল। “ভেতরে বদলানো” (mutation) কলকারীকে ছোঁয়; “নাম সরানো” (reassignment) ছোঁয় না।
React, Redux, Vue-এর মতো আধুনিক framework এই নিয়মের ওপর দাঁড়িয়ে আছে। React ঠিক করে কোন অংশ নতুন করে আঁকবে “state-এর reference বদলেছে কি না” দেখে। আপনি যদি পুরোনো object-এর ভেতরে বদল করে একই reference পাঠান, React প্রায়ই বুঝতে পারে না যে কিছু বদলেছে — পাতা পুরোনো দেখায়। তাই সেখানে নিয়ম: বদলাবেন না, নতুন object বানান।
এবার reference আর কপির সব আচরণ নিজে চালিয়ে দেখুন:
const customer = { name: "Riya", address: { district: "Khulna" } };
const alias = customer; // নতুন object নয়, একই ঠিকানা
alias.name = "Riya Akter";
console.log(customer.name, alias === customer);
const shallow = { ...customer }; // ওপরের স্তরের কপি
shallow.name = "Other";
shallow.address.district = "Jashore";
console.log(customer.name, customer.address.district);
const deep = structuredClone(customer);
deep.address.district = "Dhaka";
console.log(customer.address.district, deep.address.district);
console.log({ a: 1 } === { a: 1 }, Object.hasOwn(customer, "name"),
Object.hasOwn(customer, "toString"), "toString" in customer);
const codes = { b: 1, 10: "x", a: 2, 2: "y", "-1": "z" };
console.log(Object.keys(codes));প্রথম লাইন Riya Akter true — alias দিয়ে বদলানো নাম customer-এও। দ্বিতীয় লাইন Riya Akter Jashore — খুব মনোযোগ দিন: spread কপির name বদলানো মূলকে ছোঁয়নি, কিন্তু address.district বদলানো মূলকেও বদলে দিয়েছে! কারণটি পরের অংশে। তৃতীয় লাইন Jashore Dhaka — structuredClone-এর গভীর কপি সম্পূর্ণ আলাদা। চতুর্থ লাইন false true false true — দুটি আলাদা literal কখনো === নয়; toString নিজের নয় কিন্তু শিকলে আছে। শেষ লাইন [ '2', '10', 'b', 'a', '-1' ] — key-এর ক্রমের নিয়ম।
৬ · কপি — shallow আর deep
একটি object-এর আলাদা কপি বানানোর তিনটি সাধারণ উপায় আছে, আর দুটি গোত্রে ভাগ করা যায়:
📄 Shallow copy — ওপরের স্তর
{ ...obj } (ES2018) বা Object.assign({}, obj) (ES2015). নতুন object, কিন্তু প্রতিটি property-র মান হুবহু কপি — মান যদি আরেকটি object হয়, কপি হয় তার reference.
📚 Deep copy — সব স্তর
structuredClone(obj) — ভেতরের প্রতিটি object, array, Date, Map পর্যন্ত নতুন করে তৈরি। মূলের সাথে আর কোনো ভাগাভাগি নেই।
Shallow copy-এর ফাঁদটি এত সাধারণ যে আলাদা করে ধাপে ধাপে দেখা দরকার:
1const order = { id: 1, address: { city: "Dhaka" } };2const copy = { ...order };3copy.id = 2;4copy.address.city = "Khulna";5console.log(order.id, order.address.city);6const safe = structuredClone(order);7safe.address.city = "Sylhet";8console.log(order.address.city);
| order | |
| copy | |
| address A | |
| safe | |
| address B |
- দুই স্তরের object.
orderএকটি object, যারaddressproperty-র মান আরেকটি object — ধরুন তার নাম A.order.address-এ আছে A-এর ঠিকানা। - Spread — ওপরের স্তর কপি। নতুন object
copyতৈরি হলো।idএকটি number, তাই মান ১ কপি হলো।address-এর মান একটি reference, তাই কপি হলো ঠিকানা → A. এখন দুই object-ই একই A দেখায়। copy.id = 2— নিরাপদ।idশুধুcopy-এর নিজের ঘরে বদলাল।order.idএখনো ১.copy.address.city— ফাঁদ!copy.addressধরে A-তে যাওয়া হলো আর A-এরcityবদলানো হলো। কিন্তু A তোorder-এরও address!- ফল:
1 Khulna.idআলাদা আছে, কিন্তু শহর মূলেও বদলে গেছে। বাস্তবে: “অর্ডার কপি করে ডেলিভারি ঠিকানা বদলালাম” — আর গ্রাহকের প্রোফাইলের ঠিকানাও বদলে গেল। structuredClone— সব স্তর নতুন। এটি ভেতরে ঢুকে প্রতিটি object নতুন করে বানায়।safeএকটি নতুন object, আর তার address একটি নতুন object B (শুরুতেorder-এর বর্তমান মান “Khulna”).- B বদলালে A অক্ষত।
safe.address.city = "Sylhet"শুধু B বদলায়।order.address.cityএখনোKhulna. গভীর কপিতে কোনো ভাগাভাগি নেই।
Shallow কপি দিয়েও নিরাপদে ভেতরের স্তর বদলানো যায় — যে স্তর বদলাবেন, সেটিও spread করুন: { ...order, address: { ...order.address, city: "Khulna" } }. React-এ state বদলানোর এটিই প্রচলিত ধরন। Object.assign(target, ...sources) একই কাজ করে, তবে প্রথম argument-টিকেই বদলে দেয় — তাই Object.assign({}, a, b) লিখলে নতুন object, Object.assign(a, b) লিখলে a বদলায়। Spread আর destructuring-এর পুরো ব্যবহার পর্ব ১৭-এ।
Deep copy — structuredClone বনাম পুরোনো JSON কৌশল
structuredClone ২০২২ সালের দিকে সব প্রধান ব্রাউজারে এসেছে (Node.js-এ 17 থেকে)। এর আগে বহু বছর ধরে মানুষ গভীর কপির জন্য একটি কৌশল লিখত — JSON.parse(JSON.stringify(obj)): object-কে JSON লেখায় বদলাও, তারপর আবার পড়ো। কাজ চলে, কিন্তু চুপচাপ ডেটা হারায়:
| মূল object-এ যা আছে | JSON.parse(JSON.stringify(…)) | structuredClone(…) |
|---|---|---|
Date (অর্ডারের তারিখ) | string হয়ে যায় — আর Date নয় | Date থাকে |
undefined মানের property | key-টিই হারিয়ে যায় | থাকে |
Map, Set | খালি {} হয়ে যায় | থাকে |
NaN, Infinity | null হয়ে যায় | থাকে |
| নিজের দিকে ফেরা reference (circular) | TypeError | ঠিকঠাক কপি |
| function / method | হারিয়ে যায় | error দেয় (DataCloneError) |
| class-এর object | সাধারণ object হয়ে যায় | সাধারণ object হয়ে যায় (prototype হারায়) |
তাই নিয়ম: সাধারণ ডেটার গভীর কপি দরকার হলে structuredClone. তবে মনে রাখুন, প্রতিবার গভীর কপি করা ব্যয়বহুল — বড় ডেটায় প্রায়ই শুধু যে স্তর বদলাবেন, সেটুকু spread করাই ভালো। JSON আর structuredClone-এর বিস্তারিত পর্ব ১৯-এ।
৭ · Object-কে রক্ষা ও নিয়ন্ত্রণ — freeze, seal, descriptor আর getter/setter
একটি দোকানের সেটিংস — VAT হার, ডেলিভারি চার্জ, মুদ্রা — অ্যাপ চলার সময় কেউ ভুল করে বদলে দিলে প্রতিটি রসিদ ভুল হবে। JavaScript তিন মাত্রার সুরক্ষা দেয়:
| Method | নতুন property যোগ | মোছা | মান বদল | যাচাই |
|---|---|---|---|---|
Object.preventExtensions | না | হ্যাঁ | হ্যাঁ | Object.isExtensible |
| Object.seal | না | না | হ্যাঁ | Object.isSealed |
| Object.freeze | না | না | না | Object.isFrozen |
নিষিদ্ধ কাজ করলে কী হয়, তা নির্ভর করে mode-এর ওপর। সাধারণ script-এ কাজটি চুপচাপ উপেক্ষিত হয় — কোনো error নেই, মান বদলায় না। Strict mode-এ (ES module আর class-এর ভেতরে সবসময়) TypeError আসে, যেমন Node.js-এ: TypeError: Cannot assign to read only property 'vat' of object '#<Object>'. চুপচাপ ব্যর্থতা খুঁজে পাওয়া কঠিন, তাই আধুনিক কোড module হিসেবে লেখাই ভালো।
Freeze অগভীর (shallow). Object.freeze(settings) শুধু settings-এর নিজের property জমায়। settings.theme যদি আরেকটি object হয়, সেটি জমে না — settings.theme.dark = true দিব্যি চলে। পুরো গাছ জমাতে প্রতিটি স্তরে আলাদা করে freeze করতে হয় (একটি ছোট recursive “deepFreeze” function দিয়ে)। “Freeze করেছি, তাই কিছুই বদলাবে না” — এই ধারণাটি AI-এর লেখা কোডেও প্রায়ই দেখা যায়।
Property descriptor — প্রতিটি ঘরের লুকানো তিনটি সুইচ
এ পর্যন্ত property মানে ছিল key আর value. আসলে প্রতিটি property-র সাথে তিনটি লুকানো boolean সুইচ থাকে, যেগুলো মিলে property descriptor:
writable— মান বদলানো যাবে কি না।enumerable—Object.keys,for…in, spread আরJSON.stringify-এ দেখা যাবে কি না।configurable— property মোছা বা সুইচগুলো আবার বদলানো যাবে কি না।
Object.getOwnPropertyDescriptor({ vat: 0.15 }, "vat");
// { value: 0.15, writable: true, enumerable: true, configurable: true }
const order = { id: 1042 };
Object.defineProperty(order, "secretCode", { value: "X9" });
Object.getOwnPropertyDescriptor(order, "secretCode");
// { value: 'X9', writable: false, enumerable: false, configurable: false }
Object.keys(order); // [ 'id' ] — secretCode লুকানো
order.secretCode; // 'X9' — তবে সরাসরি পড়া যায়সাধারণভাবে (literal বা = দিয়ে) বানানো property-র তিনটি সুইচই true. কিন্তু Object.defineProperty দিয়ে বানালে, যে সুইচ উল্লেখ করেননি সেটি ডিফল্টে false — একটি সাধারণ অবাক করা ব্যাপার। Object.freeze আসলে প্রতিটি property-তে writable: false আর configurable: false বসায়, আর object-কে extension-বন্ধ করে। Built-in method-গুলো (যেমন Object.prototype.toString) এই কারণেই for…in-এ দেখা যায় না — সেগুলো enumerable: false. খেয়াল করুন, enumerable না হওয়া মানে গোপন নয় — নিরাপত্তার জন্য এর ওপর ভরসা করবেন না।
Getter আর setter — property-র মতো দেখতে, ভেতরে function
একটি অর্ডারের মোট দাম আলাদা property হিসেবে রাখলে সমস্যা: কেউ পণ্যের সংখ্যা বদলাল কিন্তু total আপডেট করতে ভুলে গেল — দুটি তথ্য আর মেলে না। ভালো সমাধান হলো total-কে প্রতিবার পড়ার সময় হিসাব করা। getter ঠিক এটিই করে: দেখতে সাধারণ property (order.total, বন্ধনী ছাড়া), কিন্তু পড়ার সময় একটি function চলে। আর setter চলে মান বসানোর সময় — যাচাই বা রূপান্তরের সুযোগ দেয়:
const customer = {
first: "Riya",
last: "Akter",
get fullName() { // পড়লে চলে
return `${this.first} ${this.last}`;
},
set fullName(value) { // বসালে চলে
const parts = value.split(" ");
this.first = parts[0];
this.last = parts[1];
},
};
customer.fullName; // "Riya Akter"
customer.fullName = "Nadia Islam"; // setter ভাগ করে দুই ঘরে রাখল
customer.first; // "Nadia"get/set লেখার ধরন ES5 (২০০৯)-এ মানদণ্ডে এসেছে। Getter-এর ভেতরে this object-কে দেখায়, তাই অন্য property থেকে হিসাব করা যায়। শুধু getter থাকলে (setter নেই) property-টি কার্যত শুধু-পড়ার: সাধারণ script-এ মান বসানো চুপচাপ উপেক্ষিত, strict mode-এ TypeError. সত্যিকারের গোপন ঘর (#private field) object literal-এ লেখা যায় না, ওটি class-এর জিনিস; class-এর getter, setter আর private field পর্ব ২৬-এ। পরের অংশের runnable উদাহরণে getter দিয়ে অর্ডারের মোট হিসাব দেখবেন।
৮ · বাস্তব ডেটা মডেল — একটি ই-কমার্স অর্ডার, Object.groupBy আর Map-এর সাথে তুলনা
এবার সব একসাথে। একটি অনলাইন দোকানের অর্ডার বাস্তবে এমন দেখায় — সার্ভার থেকে JSON হিসেবে আসে, আর JavaScript-এ হয়ে যায় nested object আর array-এর গাছ:
{
"id": 1042,
"customer": { "name": "Riya Akter", "phone": "01711-000000" },
"shipping": { "district": "Khulna", "area": "Sonadanga", "fee": 120 },
"items": [
{ "sku": "RICE-5KG", "name": "মিনিকেট চাল ৫ কেজি", "price": 420, "qty": 2 },
{ "sku": "DAL-1KG", "name": "মসুর ডাল ১ কেজি", "price": 140, "qty": 1 }
],
"payment": { "method": "bKash", "paid": true },
"status": "packed"
}এমন ডেটা নিয়ে কাজ করার কয়েকটি ভালো অভ্যাস:
- যা হিসাব করা যায়, তা জমা রাখবেন না। মোট দাম
itemsথেকে বের হয় — getter বা function দিয়ে হিসাব করুন, আলাদাtotalঘর রাখলে একসময় মিলবে না। - গভীরে নিরাপদে পড়ুন।
order.payment?.method ?? "অজানা"— কোনো স্তর না থাকলে অ্যাপ ভাঙবে না। কিন্তু সব জায়গায়?.ছিটিয়ে দেবেন না; যে ঘর অবশ্যই থাকার কথা, তা না থাকলে আগেভাগে error দেওয়াই ভালো। - Key-এর নাম স্থির রাখুন। এক জায়গায়
qty, আরেক জায়গায়quantity— ভুল বানানের keyundefinedদেয়, error নয়। পর্ব ৪৭-এর TypeScript এই ভুল লেখার সময়েই ধরে। - স্টক বদলানোর মতো mutation এক জায়গায় রাখুন। “স্টক কমাও” কাজটি একটি function-এ, যাতে কোথায় কোথায় object বদলায়, তা খুঁজে পাওয়া যায়।
Object.groupBy — অর্ডারগুলো জেলা অনুযায়ী ভাগ করা
ডেলিভারি টিম জানতে চায়: কোন জেলায় কয়টি অর্ডার? আগে এর জন্য লুপ চালিয়ে হাতে object বানাতে হতো। ES2024-এ এসেছে Object.groupBy(তালিকা, callback): callback প্রতিটি উপাদানের জন্য একটি key ফেরত দেয়, আর ফলাফল একটি object, যার প্রতিটি key-তে সেই দলের উপাদানের array. Edge/Chrome 117 আর তার পরের সংস্করণে এটি আছে; Node.js-এ আছে 21 থেকে — Node 20-এ নেই, তাই পুরোনো Node-এ চালানোর কোডে সাবধান। ফলাফল object-টির কোনো prototype নেই (Object.create(null)-এর মতো), তাই তাতে toString বা hasOwnProperty method নেই — যাচাইয়ে Object.hasOwn ব্যবহার করুন।
অর্ডারের getter, freeze-এর সীমা আর groupBy — একসাথে চালিয়ে দেখুন:
const order = {
id: 1042,
items: [{ name: "চাল", price: 80, qty: 5 }, { name: "ডাল", price: 120, qty: 2 }],
get total() {
let sum = 0;
for (const it of this.items) sum += it.price * it.qty;
return sum;
},
};
console.log(order.total);
order.items[1].qty = 3; // ডেটা বদলালে total নিজেই বদলায়
console.log(order.total, Object.keys(order));
const settings = Object.freeze({ vat: 0.15, theme: { dark: false } });
settings.vat = 0; // strict mode নয় — চুপচাপ উপেক্ষা
settings.theme.dark = true; // ভেতরের object freeze হয়নি!
console.log(settings.vat, settings.theme.dark, Object.isFrozen(settings));
const orders = [
{ id: 1, district: "ঢাকা" }, { id: 2, district: "খুলনা" }, { id: 3, district: "ঢাকা" },
];
const byDistrict = Object.groupBy(orders, (o) => o.district);
console.log(Object.keys(byDistrict), byDistrict["ঢাকা"].length);প্রথম লাইন 640 (৮০×৫ + ১২০×২)। দ্বিতীয় লাইন 760 [ 'id', 'items', 'total' ] — ডালের সংখ্যা বদলাতেই getter নতুন মোট দিল, আর getter-ও একটি enumerable property হিসেবে keys-এ আছে। তৃতীয় লাইন 0.15 true true — এই runner সাধারণ script চালায়, তাই vat বদলের চেষ্টা চুপচাপ উপেক্ষিত হলো; কিন্তু ভেতরের theme জমেনি, তাই dark বদলে গেল — অথচ isFrozen বলছে true! শেষ লাইন [ 'ঢাকা', 'খুলনা' ] 2 — দলগুলো প্রথম দেখার ক্রমে, ঢাকায় দুটি অর্ডার।
Object নাকি Map?
Object দিয়ে “নাম → মান” তালিকা (dictionary) বানানো খুব প্রচলিত — পর্ব ১১-এ lookup table হিসেবেও দেখেছেন। কিন্তু JavaScript-এ এর জন্য একটি বিশেষ জিনিস আছে: Map (ES2015). কোনটি কখন:
| প্রশ্ন | Object | Map |
|---|---|---|
| Key কী হতে পারে | শুধু string আর Symbol (সংখ্যা string হয়ে যায়) | যেকোনো মান — object, number, function |
| Key-এর ক্রম | integer-এর মতো key আগে, তারপর যোগের ক্রম | সবসময় যোগের ক্রম |
| কয়টি আছে | Object.keys(o).length | m.size |
| লুকানো inherited key | আছে ("toString" in o সত্য) | নেই |
| ঘনঘন যোগ-মোছা | ধীর হতে পারে | এর জন্যই তৈরি |
| JSON-এ পাঠানো | সরাসরি | আগে object-এ বদলাতে হয় |
সহজ নিয়ম: একটি নির্দিষ্ট “জিনিস”-এর ছবি (গ্রাহক, অর্ডার — key আগে থেকে জানা) → object. চলার সময় তৈরি হওয়া বা user-এর দেওয়া key, অথবা string নয় এমন key → Map. User-এর দেওয়া key সরাসরি সাধারণ object-এ বসানোর আরেকটি ঝুঁকি আছে: "__proto__"-এর মতো বিশেষ key prototype বদলে দিতে পারে — একে বলে prototype pollution, এটি একটি পরিচিত নিরাপত্তা দুর্বলতা। প্রতিরোধ: এমন ক্ষেত্রে Map বা Object.create(null) ব্যবহার করুন, আর বাইরের key যাচাই করুন। Map, Set আর WeakMap পর্ব ১৮-এ।
{ a: 1 } === { a: 1 }কী দেয়, কেন? —false; object-এর===তুলনা করে reference (একই object কি না), বিষয়বস্তু নয়।- Shallow আর deep copy-র পার্থক্য? — Shallow (spread,
Object.assign) শুধু ওপরের স্তর নতুন করে, ভেতরের object ভাগাভাগি থাকে; deep (structuredClone) সব স্তর নতুন করে। constদিয়ে ঘোষিত object বদলানো যায় কি? — Property বদলানো যায়; শুধু variable-টিকে নতুন object-এ সরানো যায় না। আটকাতেObject.freeze, যা অগভীর।inআরObject.hasOwn-এর পার্থক্য? —inprototype chain-ও দেখে;hasOwnশুধু নিজের property.- Dot আর bracket notation কখন কোনটি? — Key জানা ও বৈধ identifier হলে dot; variable-এ থাকা key, space বা বিশেষ চিহ্নসহ key হলে bracket.
৯ · AI-এর যুগে — AI-এর লেখা object-কোড যাচাই করা
AI টুল object নিয়ে কোড খুব দ্রুত লেখে, আর তা দেখতে পরিষ্কারও লাগে। কিন্তু reference-এর বাগগুলো চোখে পড়ে না — কোড চলে, শুধু অন্য কোথাও একটি মান অকারণে বদলে যায়। Object নিয়ে AI-এর সবচেয়ে সাধারণ ভুলগুলো:
| AI-এর ভুল | কেন সমস্যা | কী চাইবেন |
|---|---|---|
Function-এ পাওয়া object সরাসরি বদলে দেওয়া (order.total = …) | কলকারীর ডেটা গোপনে বদলায়; দুইবার ডাকলে দুইবার প্রভাব | নতুন object ফেরত — { ...order, total } |
const copy = original লিখে ভাবা কপি হলো | দুটি নাম একই object | spread বা structuredClone |
| Nested ডেটায় শুধু spread | ভেতরের স্তর এখনো ভাগাভাগি | বদলানো স্তরও spread, বা গভীর কপি |
JSON.parse(JSON.stringify(x)) দিয়ে গভীর কপি | Date string হয়, undefined আর Map হারায় | structuredClone |
for…in চালানো যাচাই ছাড়া | inherited enumerable property-ও আসে | Object.entries + for…of |
obj.hasOwnProperty(k) | Object.create(null)-এ বা ডেটার ভেতরের একই নামের key-তে ভেঙে যায় | Object.hasOwn(obj, k) |
Object.freeze দিয়ে ভাবা সব স্তর নিরাপদ | freeze অগভীর | deep freeze, বা ভেতরের স্তরও freeze |
| Method-এ arrow function | this object-কে দেখায় না | method shorthand |
AI-কে object-সংক্রান্ত কোড লেখাতে চাইলে কোন ডেটা বদলানো যাবে আর কোনটি নয়, তা আগেই বলে দিন:
AI-এর লেখা object-কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১JavaScript-এ class ছাড়াই object বানানো যায়, আর object আসলে “নাম → মান”-এর একটি তালিকা। এই নকশা কোথা থেকে এল, আর Self নামের ভাষাটির ভূমিকা কী?
বেশিরভাগ পরিচিত object-oriented ভাষা — Simula, Smalltalk, C++, Java — class-ভিত্তিক: আগে একটি নকশা (class) লিখতে হয়, তারপর সেই নকশা থেকে object তৈরি হয়। Object-এর কোন কোন field থাকবে, তা class-ই ঠিক করে দেয়, আর চলার সময় তা বদলানো যায় না।
১৯৮০-এর দশকের মাঝামাঝি Xerox PARC-এ David Ungar আর Randall Smith একটি ভিন্ন প্রশ্ন করেন: class আদৌ কি দরকার? তাঁদের ভাষা Self-এ (প্রথম প্রকাশিত গবেষণাপত্র ১৯৮৭ সালে) class নেই। আছে শুধু object, যার ভেতরে নাম দেওয়া “slot”. নতুন object বানাতে একটি বিদ্যমান object নকল (clone) করুন, আর কোনো slot না পেলে object তার “parent” object-এ খোঁজে — এটিই prototype-ভিত্তিক উত্তরাধিকার। নকশাটি সরল আর নমনীয়: সব কিছু চলার সময় বদলানো যায়।
১৯৯৫ সালে Brendan Eich যখন Netscape-এর জন্য দ্রুত একটি ভাষা বানাচ্ছিলেন, তিনি function-এর ধারণা নেন Scheme থেকে (পর্ব ১৩-এর ভাবনার প্রশ্ন ১), আর object-এর ধারণা নেন Self থেকে। ফলে JavaScript-এর object হলো একটি খোলা “property-র ব্যাগ” — অনেকটা একটি hash map, যেখানে key দিয়ে মান খোঁজা হয় — আর তার সাথে একটি লুকানো prototype link. { } লিখে মুহূর্তে object বানানো যায়, যেকোনো সময় property যোগ বা মোছা যায়। ছোট script-এর জন্য এটি দারুণ সুবিধা ছিল — ওয়েব পাতার ছোট কাজে কেউ class-এর আনুষ্ঠানিকতা চায়নি।
এই নকশার দুটি দীর্ঘমেয়াদি প্রভাব আছে। প্রথমত, JavaScript-এ object একসাথে দুটি ভূমিকা পালন করে — একটি “জিনিস”-এর ছবি (গ্রাহক, অর্ডার) আর একটি সাধারণ dictionary. এই দ্বৈত ভূমিকা থেকেই for…in-এর inherited key-এর সমস্যা, hasOwnProperty-এর জটিলতা আর prototype pollution-এর ঝুঁকি — তাই ES2015-এ আলাদা Map আনা হলো। দ্বিতীয়ত, ES2015-এর class keyword আসলে নতুন object মডেল নয়; ভেতরে এখনো সেই prototype শিকল (পর্ব ২৫, ২৬)।
মজার ব্যাপার, Self-এর আরেকটি অবদান JavaScript-কে দ্রুতও করেছে — Self-এর গবেষকরা এমন সব কৌশল আবিষ্কার করেন যা পরে V8-এ এসেছে। সেটি প্রশ্ন ৩-এ।
মূল উপলব্ধি: JavaScript-এর object class-এর ছাঁচ থেকে তৈরি জিনিস নয়, বরং একটি খোলা ব্যাগ আর একটি prototype link. এই সরলতা থেকেই এর নমনীয়তা, আর একই সরলতা থেকেই এর বেশিরভাগ ফাঁদ।
প্র ০২আজকের ওয়েবের প্রায় সব API যে JSON-এ কথা বলে, সেই JSON-এর জন্ম JavaScript-এর object literal থেকে। কীভাবে, আর JSON কেন object literal-এর চেয়ে কঠোর?
২০০০-এর দশকের শুরুতে ব্রাউজার আর সার্ভারের মধ্যে ডেটা পাঠানোর প্রধান ফরম্যাট ছিল XML — লম্বা, ট্যাগে ভরা, আর পড়তে আলাদা parser লাগত। ২০০১ সালের দিকে Douglas Crockford আর তাঁর সহকর্মীরা State Software নামের একটি প্রতিষ্ঠানে কাজ করার সময় একটি সহজ পর্যবেক্ষণ করেন: JavaScript-এর object আর array literal ইতোমধ্যেই ডেটা লেখার একটি চমৎকার ভাষা — { "name": "Riya", "orders": 3 }. ব্রাউজার এটি এমনিতেই বোঝে।
Crockford এই উপসেটটির নাম দেন JSON (JavaScript Object Notation), আর ২০০২ সালের দিকে json.org ওয়েবসাইটে এর ব্যাকরণ মাত্র কয়েকটি চিত্রে প্রকাশ করেন। তিনি নিজে প্রায়ই বলেছেন, তিনি JSON “আবিষ্কার” করেননি, “খুঁজে পেয়েছেন” — কারণ এটি আগেই ভাষার ভেতরে ছিল। পরে এটি RFC 4627 (২০০৬) হিসেবে প্রকাশিত হয়, ২০১৩ সালে ECMA-404 মানদণ্ড হয়, আর এখনকার মূল নির্দেশিকা RFC 8259 (২০১৭)। Ajax (পর্ব ০১) আর JSON একসাথে মিলে ওয়েব অ্যাপের ধরন বদলে দেয়।
শুরুর দিকে JSON পড়ার সহজ উপায় ছিল eval("(" + text + ")") — লেখাটিকে JavaScript কোড হিসেবে চালিয়ে দেওয়া। এটি বিপজ্জনক: লেখার ভেতরে ডেটার বদলে কোড থাকলে সেটিও চলে যেত। তাই Crockford একটি নিরাপদ parser লাইব্রেরি (json2.js) প্রকাশ করেন, আর ES5 (২০০৯) ভাষার ভেতরেই JSON.parse ও JSON.stringify যোগ করে।
JSON ইচ্ছা করেই object literal-এর চেয়ে কঠোর, কারণ এটিকে সব ভাষায় পড়তে পারতে হবে: key-কে অবশ্যই double quote-এ রাখতে হয়; single quote, মন্তব্য আর শেষে বাড়তি কমা নিষিদ্ধ; function, undefined, Date, NaN-এর কোনো রূপ নেই। এজন্যই অংশ ৬-এ দেখেছেন, JSON দিয়ে কপি করলে তারিখ string হয়ে যায় আর undefined হারায় — JSON-এ এগুলো লেখারই উপায় নেই। এই কঠোরতাই JSON-কে Python, Java, Go, PHP — সব ভাষার সাধারণ ভাষা বানিয়েছে।
মূল উপলব্ধি: JSON হলো object literal-এর একটি সরল, ভাষা-নিরপেক্ষ উপসেট। একটি ভাষার ভালো অংশ ছোট আর কঠোর করে নিলে তা পুরো শিল্পের মানদণ্ড হয়ে উঠতে পারে — আর সেই কঠোরতার সীমা জানা থাকলে JSON-এ কী হারায়, তা আগেই বোঝা যায়।
প্র ০৩Object যদি একটি hash map হয়, তাহলে order.total পড়তে প্রতিবার key খুঁজতে হওয়ার কথা — তবুও আধুনিক JavaScript এত দ্রুত কেন? Property যোগের ক্রম কেন গতিতে প্রভাব ফেলে?
সহজ ভাবে বানালে প্রতিটি JavaScript object হতো একটি hash table: order.total পড়তে “total” string-এর hash হিসাব, টেবিলে খোঁজা, সংঘর্ষ সামলানো। C বা Java-তে একই কাজ একটি নির্দিষ্ট offset থেকে মেমরি পড়া — অনেক গুণ দ্রুত, কারণ compiler আগে থেকেই জানে field কোথায়।
এই সমস্যা প্রথম গুরুত্ব দিয়ে সমাধান করেন Self-এর গবেষকরা। ১৯৮৯ সালের দিকে Craig Chambers, David Ungar আর Elgin Lee দেখান, একই ধরনের object-গুলোর গঠন (কোন slot কোন অবস্থানে) একটি ভাগের “map”-এ রাখা যায়, আর object নিজে শুধু মানগুলো ধরে রাখে। পরে Urs Hölzle-রা যোগ করেন polymorphic inline cache. এই দলের কয়েকজন — বিশেষত Lars Bak — পরে Google-এ ২০০৮ সালে V8 তৈরিতে নেতৃত্ব দেন, আর এই ধারণাগুলো JavaScript-এ নিয়ে আসেন। V8-এ এর নাম hidden class (অন্য engine-এ “shape” বা “structure”), যা পর্ব ০৫-এ দেখেছেন।
কাজটি এরকম: { } একটি খালি shape দিয়ে শুরু হয়। x যোগ হলে নতুন shape “x অবস্থান ০-এ”, তারপর y যোগ হলে “x ০-এ, y ১-এ”। একই ক্রমে বানানো সব object একই shape ভাগ করে। কোনো function যদি বারবার একই shape-এর object পায়, inline cache মনে রাখে “এই shape-এ total অবস্থান ২-এ”, আর পরের বার hash খোঁজা ছাড়াই সরাসরি পড়ে — প্রায় C-এর গতি।
এখান থেকেই কয়েকটি ব্যবহারিক পরামর্শ আসে। (১) একই ধরনের object একই ক্রমে একই property দিয়ে বানান — { x, y } আর { y, x } দুটি আলাদা shape. (২) সব property শুরুতেই দিন; পরে পরে যোগ করলে shape-এর শিকল লম্বা হয়। (৩) delete এড়ান — এটি অনেক সময় object-কে ধীর “dictionary mode”-এ (আসল hash table) পাঠিয়ে দেয়। (৪) একটি function-এ নানা আকারের object পাঠালে inline cache “megamorphic” হয়ে ধীর পথে চলে যায়। তবে মনে রাখবেন, এগুলো engine-এর বাস্তবায়নের খুঁটিনাটি, ভাষার নিয়ম নয়; আগে পরিষ্কার কোড, তারপর মেপে দেখে (পর্ব ৫৩) optimization.
মূল উপলব্ধি: ভাষা আপনাকে hash map-এর স্বাধীনতা দেয়, আর engine চুপচাপ ধরে নেয় যে আপনি সেই স্বাধীনতা খুব একটা ব্যবহার করবেন না। স্থির আকারের object লিখলে আপনি engine-এর অনুমানকে সত্যি করেন — আর গতি পান বিনামূল্যে।
প্র ০৪Getter/setter আর Object.defineProperty দেখতে ছোট বৈশিষ্ট্য। কিন্তু এগুলো দিয়ে Vue.js-এর মতো একটি গোটা framework-এর “reactivity” তৈরি হয়েছিল — কীভাবে, আর এর সীমা কোথায় ছিল?
২০০৯ সালের আগে মানদণ্ডে getter বা setter ছিল না। Mozilla-র browser-এ অ-মানক __defineGetter__ আর __defineSetter__ ছিল, কিন্তু সব জায়গায় চলত না। ES5 (২০০৯) দুটি জিনিস মানদণ্ডে আনে: object literal-এ get/set লেখার ধরন, আর Object.defineProperty, যা দিয়ে চলার সময় যেকোনো property-কে getter/setter-এ বদলানো যায়, সাথে writable/enumerable/configurable সুইচ। একই সংস্করণে আসে Object.freeze, Object.seal, Object.keys আর Object.create.
এর তাৎপর্য বড়: এখন একটি সাধারণ দেখতে state.count = 5 লেখার সময় আপনার কোড চালানো সম্ভব। ২০১৪ সালে Evan You প্রকাশ করেন Vue.js, আর এর প্রথম দুই প্রধান সংস্করণ (১.x আর ২০১৬ সালের ২.x) এই কৌশলের ওপর দাঁড়িয়ে। Vue component-এর data-র প্রতিটি property-কে Object.defineProperty দিয়ে getter/setter-এ বদলে দিত। Getter চললে Vue লিখে রাখত “এই পাতার অংশ এই property পড়েছে”; setter চললে বলত “property বদলেছে — যারা পড়েছিল, তাদের নতুন করে আঁকো”। ডেভেলপার সাধারণ JavaScript object লিখতেন, আর পাতা নিজে থেকেই আপডেট হতো — জাদুর মতো লাগত।
কিন্তু এর সীমা সরাসরি defineProperty থেকেই আসে: এটি কাজ করে বিদ্যমান property-তে। শুরুর পরে নতুন property যোগ করলে (state.newKey = 1) বা delete করলে Vue জানতে পারত না, কারণ সেখানে কোনো setter বসানো ছিল না। Array-এর index ধরে মান বসানো (list[3] = x) আর length বদলও ধরা যেত না। তাই Vue 2-তে আলাদা Vue.set আর Vue.delete ব্যবহার করতে হতো, আর array-র push-এর মতো method-গুলোকে Vue নিজে মুড়ে দিত। এছাড়া ES5-এর এই বৈশিষ্ট্য পুরোনো ব্রাউজারে polyfill করা যায় না বলে Vue IE8 সমর্থন করেনি।
সমাধান এল ES2015-এর Proxy দিয়ে — যা একটি গোটা object-এর সব কাজ (পড়া, লেখা, যোগ, মোছা) ধরতে পারে। Vue 3 (২০২০) reactivity Proxy দিয়ে নতুন করে লেখে, আর ওপরের সীমাগুলো চলে যায়। Proxy আর Reflect পর্ব ২৮-এ।
মূল উপলব্ধি: ভাষার ছোট একটি hook — “property পড়া বা লেখার সময় আমার কোড চালাও” — থেকে বড় framework জন্ম নিতে পারে, আর সেই framework-এর সীমা হুবহু সেই hook-এর সীমা। কোনো লাইব্রেরির অদ্ভুত নিয়ম বুঝতে প্রায়ই তার নিচের ভাষা-বৈশিষ্ট্যটি দেখতে হয়।
প্র ০৫অর্ডার ভাগ করার method-টি কেন orders.groupBy(…) নয়, বরং Object.groupBy(orders, …)? একটি নামের পেছনে ওয়েবের সামঞ্জস্যের (web compatibility) কী গল্প আছে?
স্বাভাবিক নকশা হতো array-এর method: orders.groupBy(o => o.district), ঠিক map বা filter-এর মতো। TC39-এ (পর্ব ০১) প্রস্তাবটি শুরুতে ঠিক সেভাবেই এসেছিল — Array.prototype.groupBy. কিন্তু ওয়েবে একটি অলিখিত নিয়ম আছে: “ওয়েব ভাঙবেন না”। কোটি কোটি পুরোনো পাতা আর কেউ আপডেট করবে না; ব্রাউজারে নতুন কিছু যোগ করে যদি সেগুলো ভেঙে যায়, তাহলে দোষ ব্রাউজারের।
সমস্যাটি পুরোনো লাইব্রেরিগুলোর একটি অভ্যাস: তারা নিজেরাই built-in prototype-এ method বসাত, কখনো “আগে থেকে না থাকলে” শর্তে, কখনো নিজের আলাদা আচরণে। ব্রাউজারে চালু করার পরীক্ষায় দেখা গেল, Sugar.js-এর মতো পুরোনো লাইব্রেরি — যেগুলো Array.prototype-এ নিজের method বসায় — ব্যবহার করা কিছু সাইট নতুন built-in method-এর সাথে সংঘর্ষে ভেঙে যাচ্ছে। নাম বদলে group আর groupToMap করার পরেও একই রকম সংঘর্ষের খবর আসে। শেষে ২০২৩ সালে কমিটি পুরো পথই বদলায়: array-এর prototype-এ নয়, static function হিসেবে — Object.groupBy আর Map.groupBy. Static নামে কোনো পুরোনো লাইব্রেরির array prototype-এর সাথে সংঘর্ষের ভয় নেই, আর এটি যেকোনো iterable নিতে পারে। এটি ES2024-এর অংশ হয়।
এমন ঘটনা এটিই প্রথম নয়। ২০১৮ সালে Array.prototype.flatten একই রকম সমস্যায় পড়ে — MooTools লাইব্রেরি ব্যবহার করা পুরোনো সাইট ভাঙছিল। মজা করে কেউ প্রস্তাব দিয়েছিলেন নাম রাখা হোক smoosh; ঘটনাটি “SmooshGate” নামে পরিচিত, আর শেষে নাম হয় flat. Array.prototype.contains-ও একই কারণে includes নামে এসেছে।
শিক্ষাটি আপনার নিজের কোডের জন্যও: built-in object-এর prototype-এ (Array, Object, String) কখনো নিজের method বসাবেন না। আজ সুবিধাজনক লাগলেও কাল ভাষা একই নামে আলাদা আচরণের method আনতে পারে — তখন হয় আপনার কোড ভাঙবে, নয় পুরো ভাষার অগ্রগতি আটকে যাবে।
মূল উপলব্ধি: একটি জীবন্ত প্ল্যাটফর্মে নতুন বৈশিষ্ট্যের নাম শুধু সুন্দর হলেই চলে না — অতীতের প্রতিটি লাইব্রেরির সাথে সংঘর্ষও এড়াতে হয়। Object.groupBy-এর একটু অস্বাভাবিক চেহারা আসলে পুরোনো ওয়েবকে বাঁচিয়ে রাখার দাম।
অনুশীলন
এগুলোর বেশিরভাগে কোড লিখতে হবে না — আগে অনুমান করুন, তারপর console-এ চালিয়ে মিলিয়ে নিন।
-
আউটপুট অনুমান করুন:
const p = { price: 100 }; const q = p; const r = { ...p }; q.price = 150; r.price = 90;— এখনp.price,q.price,r.price,p === qআরp === rকী কী?p.priceআরq.priceদুটোই ১৫০ —qএকই object-এর দ্বিতীয় নাম।r.price৯০ — spread একটি নতুন object বানিয়েছিল, তাই তার বদল মূলকে ছোঁয় না।p === qহলোtrue(একই reference),p === rহলোfalse(আলাদা object, যদিও শুরুতে মান এক ছিল)। -
বাগ খুঁজুন: একজন লিখেছেন
const key = "district"; console.log(customer.key);আর পাচ্ছেনundefined, যদিওcustomer-এdistrictআছে। আরেকজন লিখেছেনconst updated = { ...order }; updated.shipping.fee = 0;— আর দেখছেন মূল অর্ডারেও ডেলিভারি চার্জ শূন্য। দুটি কারণ ও সমাধান বলুন।প্রথমটিতে dot-এর পরের নাম আক্ষরিক —
customer.keyখোঁজে"key"নামের property. Variable-এর মান দিয়ে খুঁজতে bracket লাগে:customer[key]. দ্বিতীয়টিতে spread শুধু ওপরের স্তর কপি করেছে;shippingএখনো দুই object-এ একই। সমাধান: বদলানো স্তরও spread করুন —{ ...order, shipping: { ...order.shipping, fee: 0 } }— অথবাstructuredClone(order). -
যাচাই আর ক্রম:
const roll = { 25: "Nila", 3: "Arif", leader: "Tania", 10: "Sumon" };—Object.keys(roll)কী দেবে?"toString" in rollআরObject.hasOwn(roll, "toString")-এর উত্তর কী? আরroll.leader = undefinedকরার পরেObject.hasOwn(roll, "leader")কী দেবে?[ '3', '10', '25', 'leader' ]— integer-এর মতো key আগে ছোট থেকে বড় ক্রমে, তারপর বাকি key যোগের ক্রমে।"toString" in rollহলোtrue(prototype chain-এ আছে),Object.hasOwnহলোfalse(নিজের নয়)।undefinedবসালে key মুছে যায় না, তাইObject.hasOwn(roll, "leader")এখনোtrue; key সরাতেdelete roll.leaderলাগে। -
ছোট function লিখুন:
restock(stock, item, qty)লিখুন, যা একটি স্টক object (যেমন{ rice: 50, dal: 0 }) নিয়ে নির্দিষ্ট পণ্যের সংখ্যা বাড়িয়ে নতুন object ফেরত দেবে — মূল object বদলাবে না। পণ্যটি আগে না থাকলে নতুন key যোগ হবে। তারপর মূল আর নতুন object দুটিই ছেপে মিলিয়ে নিন।একটি সমাধান:
function restock(stock, item, qty) { return { ...stock, [item]: (stock[item] ?? 0) + qty }; }. এখানে তিনটি ধারণা একসাথে: spread দিয়ে নতুন object (মূল অক্ষত), computed key[item](কোন পণ্য, তা চলার সময় ঠিক হয়), আর?? 0(পণ্য না থাকলেundefined + qtyহয়েNaNহওয়া ঠেকায়)।restock({ rice: 50 }, "dal", 10)দেয়{ rice: 50, dal: 10 }, আর মূল object এখনো{ rice: 50 }.
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমJavaScript-এ obj.x পড়লে engine কোন ক্রমে খোঁজে, সাজান (▲▼ চেপে বা টেনে)।
- 👤 Object-এর নিজের property-গুলোতে খোঁজা
- 🔗 না পেলে object-এর prototype-এ যাওয়া
- 🏛️ তারপর শিকল ধরে Object.prototype পর্যন্ত খোঁজা
- ⛔ শিকলের শেষে null-এ পৌঁছানো
- ❓ কোথাও না পেলে undefined ফেরত দেওয়া
const a = { n: 1 }; const b = a; b.n = 2; — এরপর JavaScript-এ a.n কত?
- 1
- 2
- undefined
- TypeError, কারণ a একটি const
const o = { info: { city: "Dhaka" } }; const c = { ...o }; c.info.city = "Rajshahi"; — এরপর o.info.city কী?
- "Dhaka", কারণ spread পুরো object কপি করে
- TypeError
- undefined
- "Rajshahi", কারণ ভেতরের object ভাগাভাগি হয়
JavaScript-এ const k = "price"; থাকলে object-এর price property পড়ার সঠিক উপায় কোনটি?
obj[k]- obj.k
- obj["k"]
- obj.[k]
const s = Object.freeze({ vat: 0.15, theme: { dark: false } }); — সাধারণ (non-strict) script-এ s.vat = 0; s.theme.dark = true; চালানোর পরে কী সত্য?
- vat 0.15 আর dark false — দুটোই অপরিবর্তিত
- vat 0 আর dark true — দুটোই বদলেছে
- vat 0.15 থাকে, কিন্তু dark true হয়ে যায়
- প্রথম লাইনেই TypeError
একটি সাধারণ object literal const u = { name: "Rafi" }-এর জন্য JavaScript-এ কোনটি true দেয়?
- Object.hasOwn(u, "toString")
- "toString" in u
- Object.keys(u).length === 2
- u === { name: "Rafi" }
একটি অর্ডারের Date আর Map-সহ গভীর কপি দরকার। JavaScript-এ কোনটি সবচেয়ে উপযুক্ত?
- { ...order }
- JSON.parse(JSON.stringify(order))
- Object.assign({}, order)
- structuredClone(order)
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখতে পাবেন।
মনে রাখুন
- Object = নাম দেওয়া property-র সংগ্রহ (key → value); key সবসময় string বা Symbol. Method হলো function মানের property — লিখুন shorthand দিয়ে,
this= ডটের বাঁ পাশের object. - Dot জানা ও সাধারণ নামের জন্য; bracket variable-এ থাকা key, আর space বা হাইফেনযুক্ত key-এর জন্য। ভুল key চুপচাপ
undefinedদেয়। - নিজের property যাচাইয়ে
Object.hasOwn; ঘুরতেObject.keys/values/entries. Key-এর ক্রম: integer-এর মতো key আগে, তারপর যোগের ক্রম। - Variable-এ থাকে reference:
b = aকপি নয়,===পরিচয় তুলনা করে, function-এ পাঠানো object বদলালে কলকারীও দেখে। - Spread/
assign= shallow,structuredClone= deep;freeze-ও অগভীর। হিসাব করা মানের জন্য getter, চলার সময়ের key-এর জন্য Map.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১৫ · array — তৈরি, বদলানো আর খোঁজাপরবর্তী পর্বক্রমানুসারে সাজানো ডেটা, আর কোন method মূল array বদলায়।
- JavaScript প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Python-এর dict — key, value আর hashingতুলনা করুনএকই “নাম → মান” ধারণা, যেকোনো hashable key সহ।
- Python-এ mutability আর কপিতুলনা করুনReference, shallow আর deep copy — অন্য ভাষার চোখে একই ফাঁদ।
- Java-র class আর objectপাশের ট্র্যাকClass-ভিত্তিক জগতে object, reference আর equals বনাম ==.