Skip to content

Latest commit

 

History

History
241 lines (156 loc) · 14.6 KB

File metadata and controls

241 lines (156 loc) · 14.6 KB

অবজেক্ট রেফারেন্স এবং কপি করা

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

একটি মান কপি করলে কি হয় তা একটু গভীরভাবে দেখলেই আমরা এটি আরো ভালোভাবে বুঝতে পারব।

স্ট্রিং এর মত একটি প্রিমিটিভ নিয়েই শুরু করা যাক।

এখানে আমরা message এর একটি কপি কে phrase এ রাখলামঃ

let message = "Hello!";
let phrase = message;

এর ফলে আমরা দুটি স্বাধীন ভেরিয়েবল আছে, প্রতিটি "হেলো" স্ট্রিংটি সংরক্ষণ করছে ।

খুব স্বাবাভিক ই মনে হচ্ছে, তাই না?

অবজেক্ট রা এমন নয়।

একটি অবজেক্ট এর জন্য নির্ধারিত ভেরিয়েবল সেই অবজেক্ট কে সংরক্ষণ করে না, বরং এর ঠিকানা সংরক্ষণ করে, অন্য কথায় এটির একটি "রেফারেন্স"।

এমন একটি ভেরিয়েবল এর উদাহরণ দেখা যাকঃ

let user = {
  name: "John"
};

এটি স্মৃতি তে কিভাবে সংরক্ষণ করা হয় তা নিচের ছবিতে দেখানো হলোঃ

অবজেক্ট টি স্মৃতির কোথাও সংরক্ষণ করা আছে (ডানে), আর user ভেরিয়েবল এর কাছে এর একটি রেফারেন্স আছে।

আমরা user এর মতো অবজেক্ট কে একটি কাগজের টুকরো হিসেবে ভাবতে পারি, যাতে ঠিকানা লেখা আছে ।

যখন আমরা অবজেক্ট এর উপরে কোন কাজ করি, যেমন user.name প্রপার্টি কে নেয়া, জাভাস্ক্রিপ্ট ইঞ্জিন ঠিকানা থেকে অবজেক্ট টি বের তার উপরে কাজ টি সম্পাদন করে ।

এটি গুরুতপুর্ন কারণ

যখন কোনও বস্তুর ভেরিয়েবল কপি করা হয় - রেফারেন্সটি কপি হয়, বস্তুটি নকল হয় না ।

যেমন :

let user = { name: "John" };

let admin = user; // রেফারেন্স কপি হলো

এখন আমাদের দুটি ভেরিয়েবল রয়েছে, প্রত্যেকেই একই বস্তুর রেফারেন্স:

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

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

let user = { name: 'John' };

let admin = user;

*!*
admin.name = 'Pete'; // এডমিন রেফারেন্সের এর মাধ্যমে পরিবর্তন হলো
*/!*

alert(*!*user.name*/!*); // 'Pete', পরিবর্তন টি "user" রেফারেন্স থেকে দেখা যাচ্ছে

উপরের উদাহরণটি প্রমাণ করে যে এখানে কেবল একটি অবজেক্ট রয়েছে। যেন আমাদের একি কক্ষের দুটি চাবি আছে আর আমরা একটি চাবি (admin) দিয়ে কক্ষে প্রবেশ করেছি। পরে অন্যটি (user) দিয়ে কক্ষের ভেতরে দেখেছি।

রেফারেন্স এর মাধ্যমে তুলনা

যদি দুটি অবজেক্ট একই বস্তু হয়, শুধুমাত্র তাহলেই তারা "ইকুয়াল"।

যেমন, এখানে a এবং b একই অবজেক্ট কে রেফারেন্স করে, সুতরাং তারা ইকুয়াল:

let a = {};
let b = a; // রেফারেন্স কপি হোলো

alert( a == b ); // true, দুটি অবজেক্ট ই সমান
alert( a === b ); // true

আর এখানে দুটি অবজেক্ট সমান নয়, যদিও তারা দেখতে একই (দুজনই খালি) :

let a = {};
let b = {}; // দুটি স্বাধীন অবজেক্ট

alert( a == b ); // false

obj1 > obj2 এর মত তুলনা এর জন্য অথবা কোন প্রিমিটিভ এর সাথে তুলনা করার জন্য obj == 5, অবজেক্ট কে প্রিমিটিভ এ রূপান্তর করা হয়। অবজেক্ট গুলোকে কিভাবে তুলনা করা হয় তা সম্পর্কে আমরা শিগ্রই জানব, কিন্তু সত্যি বলতে এই ধরনের তুলনা খুব কমই প্রয়োজন হয়, সাধারণত এগুলি ভুলক্রমে চলে আসে।

ক্লোন করা ও মিলিত করা, Object.assign

তো আমরা জানলাম অবজেক্ট ভেরিএবল কে কপি করলে তা শুধু একটি নতুন রেফারেন্স তৈরি করে।

কিন্তু আমাদের যদি অবজেক্ট এর স্বাধীন নকল বা ক্লোন তৈরি করতে হয় তাহলে আমরা কি করব?

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

কিন্তু আমরা যদি আসলেই এটি চাই তাহলে আমাদের নতুন একটি অবজেক্ট বানাতে হবে, ও মুল অবজেক্ট টির সম্পূর্ণ কাঠামো কে নকল করে এর সকল প্রপার্টির প্রিমিটিভ স্তরে প্রতিলিতি তৈরি করতে হবে।

যেমন:

let user = {
  name: "John",
  age: 30
};

*!*
let clone = {}; // নতুন খালি অবজেক্ট 

// এখন user অবজেক্ট এর সকল প্রপার্টি কে clone অবজেক্ট এ কপি করি
for (let key in user) {
  clone[key] = user[key];
}
*/!*


// এখন ক্লোন হচ্ছে একটি স্বাধীন অবজেক্ট যার কন্টেন্ট ইউজার এর সমান 
clone.name = "Pete"; // ডাটা পরিবর্তন করলাম 

alert( user.name ); // মুল অবজেক্ট এ এখনো John

আমরা এর জন্য Object.assign মেথড টিও ব্যাবহার করতে পারি।

এর সিনট্যাক্স হলো:

Object.assign(dest, [src1, src2, src3...])
  • প্রথম আর্গুমেন্ট dest হলো টার্গেট অবজেক্ট ।
  • বাকি আর্গুমেন্ট গুলো src1, ..., srcN (যতো প্রয়োজন দেয়া যাবে) হলো মুল অবজেক্ট।
  • এটি মুল অবজেক্ট এর সকল প্রপার্টি src1, ..., srcN টার্গেট dest এ কপি করে। অন্য কথায়, দ্বিতীয় আর্গুমেন্ট থেকে বাকি সকল আর্গুমেন্ট এর প্রপার্টি গুলো প্রথম অবজেক্ট এ কপি হয়।
  • এই কল টি dest কে রিটার্ন করে।

আমরা এটি ব্যাবহার করে একাধিক অবজেক্টকে একটি অবজেক্ট এ মিলিত করতে পারি:

let user = { name: "John" };

let permissions1 = { canView: true };
let permissions2 = { canEdit: true };

*!*
// permissions1 ও permissions2 এর সকল প্রপার্টি কে user এ কপি করে
Object.assign(user, permissions1, permissions2);
*/!*

// এখন user = { name: "John", canView: true, canEdit: true }

কপি করা প্রপার্টি যদি ইতিমধ্যেই থেকে থাকে থাকলে এটি ওভাররাইট হয়ে যাবে:

let user = { name: "John" };

Object.assign(user, { name: "Pete" });

alert(user.name); // এখন user = { name: "Pete" }

আমরা for..in এর জায়গায় Object.assign ব্যাবহার করে সাধারণ ক্লোনিং করতে পারি :

let user = {
  name: "John",
  age: 30
};

*!*
let clone = Object.assign({}, user);
*/!*

এটি user এর সকল প্রপার্টি কে খালি অবজেক্ট এ কপি করে তাকে রিটার্ন করে ।

অভ্যন্তরীণ ক্লোনিং (Nested cloning)

এতক্ষণ পর্যন্ত আমরা ধরে নিয়েছিলাম যে user এর সকল প্রপার্টি ই প্রিমিটিভ । কিন্তু প্রপার্টি গুলো তো অন্যান্য অবজেক্ট এর রেফারেন্স ও হতে পারে । সেক্ষেত্রে আমরা কি করবো?

যেমন:

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

alert( user.sizes.height ); // 182

এখন এটি clone.sizes = user.sizes কে কপি করার জন্য যথেষ্ট নয়, কারণ user.sizes হলো একটি অবজেক্ট, এটি রেফারেন্সের মাধ্যমে কপি হবে। সুতরাং cloneuser একই size শেয়ার করবে:

এমন:

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

let clone = Object.assign({}, user);

alert( user.sizes === clone.sizes ); // true, একই অবজেক্ট

// user এবং clone size কে শেয়ার করে
user.sizes.width++;       // একটি জায়গা থেকে প্রপার্টি পরিবর্তন
alert(clone.sizes.width); // 51, অন্য জায়গায় রেসাল্ট দেখা

এটি সমাধান করার জন্য আমাদের একটি ক্লোনিং লুপ ব্যাবহার করা লাগবে যা user[key] এর প্রত্যেক মান কে পরীক্ষা করবে, এবং যদি এটি অবজেক্ট হয়, তাহলে এর স্ট্রাকচার কেও কপি করবে। একে বলে "ডিপ ক্লোনিং"।

আমরা রিকার্সন ব্যাবহার করে এটি তৈরি করতে পারি অথবা ইতিমধ্যেই বাস্তবায়িত একটি ব্যাবহার করতে পারি, যেমন lodash এর _.cloneDeep(obj) ফাংশন।

সংক্ষিপ্ত

অবজেক্ট গুলো রেফারেন্স এর মাধ্যমে কপি হয়। অন্য কথায়, একটি ভেরিয়েবল অবজেক্ট এর মান সংরক্ষণ করে না , বরং একটি রেফারেন্স (মেমোরি এড্রেস) সংরক্ষণ করে। সুতরাং এই ধরনের ভেরিয়েবল কে কপি করলে অবজেক্ট কপি হয় না বরং রেফারেন্স কপি হয়।

কপি করা রেফারেন্সে এর মাধ্যমে করে সকল কাজ (যেমন প্রপার্টি যোগ করা/মোছা) একই অবজেক্ট এ সম্পাদিত হয়।

একটি "বাস্তব কপি" (ক্লোন) তৈরি করতে আমরা ব্যাবহার করতে পারি Object.assign যাকে "শ্যালো কপি"(অভ্যন্তরীণ অবজেক্ট রেফারেন্সের মাধ্যমে কপি হয়) বলা হয় অথবা আমরা ব্যাবহার করতে পারি _.cloneDeep(obj) এর মত "ডিপ ক্লোনিং" ফাংশন।