مقدمه

در درس قبلی با مفهوم Object و روش‌های ساخت اشیاء و پراپرتی‌ها آشنا شدیم. یاد گرفتیم که چطور می‌توانیم مجموعه‌ای از داده‌های مرتبط را در قالب یک شیء نگهداری کنیم و ساختارهای پیچیده‌تری بسازیم. اما دانستن نحوه ساخت شیء کافی نیست؛ باید بدانیم این اشیاء چگونه در حافظه ذخیره و مدیریت می‌شوند و چرا رفتارشان در جاوااسکریپت با نوع‌های Primitive یا اولیه (مثل number و string) فرق دارد. در این درس با مفهوم «ارجاعی بودن» اشیاء آشنا می‌شویم و دقیقاً بررسی می‌کنیم که چرا تخصیص، مقایسه و کپی‌برداری از اشیاء کاملاً با مقادیر اولیه متفاوت است. در انتها روش‌های حرفه‌ای cloning شیء را هم یاد می‌گیریم تا از باگ‌های رایج جلوگیری کنیم.

تفاوت رفتار اشیاء با مقادیر Primitive

مقادیر اولیه (مثل number، string، boolean) در جاوااسکریپت به صورت مستقیم در متغیر ذخیره می‌شوند. هر زمان که یک مقدار اولیه را به متغیر دیگری انتساب می‌دهید، در واقع یک کپی کاملاً مستقل از آن مقدار ساخته می‌شود. اما اشیاء از نوع ارجاعی (reference) هستند. وقتی یک شیء را به متغیر دیگری تخصیص می‌دهید، فقط «ارجاع» یا اشاره به آن شیء منتقل می‌شود؛ پس هر دو متغیر دقیقاً به یک محل حافظه اشاره می‌کنند. بنابراین، هر تغییر روی یکی از متغیرها روی دیگری هم اثر می‌گذارد.

این تفاوت باعث می‌شود که رفتار عملیات‌هایی مثل تخصیص، مقایسه و کپی‌برداری برای اشیاء و نوع‌های اولیه کاملاً متفاوت باشد. هر یک از این رفتارها را به طور جداگانه با مثال بررسی می‌کنیم.

تخصیص (Assignment)

تخصیص یک مقدار اولیه به متغیر جدید یعنی ایجاد یک کپی کاملاً مستقل. اما در مورد اشیاء، فقط ارجاع به محل حافظه شیء کپی می‌شود. بنابراین هر تغییری روی یکی، روی دیگری هم اثر می‌گذارد.

Copy Icon JAVASCRIPT
let a = 10;
let b = a;
b = 20;
console.log(a); // 10
console.log(b); // 20

let user1 = {name: "Ali"};
let user2 = user1;
user2.name = "Sara";
console.log(user1.name); // "Sara"
console.log(user2.name); // "Sara"

در این مثال، تغییر مقدار b روی a اثر ندارد (کپی مستقل). اما هر تغییری روی user2 دقیقاً روی user1 هم اثر دارد چون هر دو به یک شیء مشترک اشاره دارند.

مقایسه (Comparison)

مقایسه مقادیر اولیه با عملگر === یا == براساس مقدار واقعی انجام می‌شود. اما مقایسه اشیاء همیشه بر اساس ارجاع است؛ یعنی اگر و فقط اگر هر دو متغیر به یک شیء واحد (یک محل حافظه) اشاره کنند نتیجه true است. در غیر این صورت، حتی اگر پراپرتی‌های هر دو شیء‌ کاملاً یگسان باشد، حاصل مقایسه false خواهد بود.

Copy Icon JAVASCRIPT
let n1 = 42;
let n2 = 42;
console.log(n1 === n2); // true

let user1 = {name: "Ali"};
let user2 = {name: "Ali"};
console.log(user1 === user2); // false

let user3 = user1;
console.log(user1 === user3); // true

همانطور که می‌بینید، مقایسه دو مقدار اولیه با مقدار یکسان نتیجه true می‌دهد، اما مقایسه دو شیء مستقل، حتی اگر پراپرتی‌هایشان یکی باشد همیشه false است، چون به محل‌های مختلفی در حافظه اشاره دارند. فقط زمانی که هر دو متغیر به یک شیء واحد اشاره کنند، نتیجه مقایسه true خواهد بود.

کپی کردن (Cloning)

دیدیم که وقتی یک شیء را به متغیر دیگر اختصاص می‌دهید، فقط ارجاع (reference) منتقل می‌شود و شیء جدید ساخته نمی‌شود. اگر می‌خواهید یک شیء جدید کاملاً مستقل بسازید، باید عملیات cloning انجام دهید. کپی‌برداری (clone) به دو شکل کپی سطحی (shallow) و کپی عمیق (deep) انجام می‌شود.

کپی سطحی با Object.assign

متد Object.assign() یک کپی سطحی از شیء ایجاد می‌کند. این یعنی فقط لایه اول شیء کپی می‌شود و اگر شیء شامل اشیاء تو در تو باشد، ارجاع به آن‌ها کپی می‌شود نه خود آن‌ها. بنابراین تغییرات در لایه‌های درونی روی شیء اصلی تأثیر می‌گذارد.

Copy Icon JAVASCRIPT
let person = {
  name: "Reza",
  address: { city: "Tehran" }
};
let clone = Object.assign({}, person);
clone.address.city = "Tabriz";
console.log(person.address.city); // "Tabriz"

در این مثال، تغییر در clone.address.city باعث تغییر در person.address.city هم می‌شود چون هر دو به همان شیء داخلی address اشاره دارند. برای کپی عمیق باید از روش‌های دیگری استفاده کنیم.

کپی عمیق با structuredClone

متد structuredClone() در جاوااسکریپت مدرن، همه‌ی لایه‌های شیء را به صورت بازگشتی (recursive) و کاملاً مستقل کپی می‌کند. مثال زیر را ببینید.

Copy Icon JAVASCRIPT
let person = {
  name: "Sara",
  address: { city: "Shiraz" }
};
let deepClone = structuredClone(person);
deepClone.address.city = "Isfahan";
console.log(person.address.city); // "Shiraz"

structuredClone() یک روش امن و مدرن برای کپی عمیق اشیاء در جاوااسکریپت است. این متد به شما این امکان را می‌دهد که بدون نگرانی از تغییرات ناخواسته در اشیاء اصلی، نسخه‌های مستقل ایجاد کنید.

تفاوت رفتار ارجاعی اشیاء با مقادیر اولیه، پایه‌ی طراحی کدهای امن و بدون باگ در جاوااسکریپت است. همیشه هنگام تخصیص، مقایسه و کپی اشیاء به ارجاعی بودن آن‌ها دقت کنید و برای کپی مستقل، از روش‌های امن cloning بهره ببرید.

در درس بعد با مفهوم متدها و کاربرد کلیدواژه this در اشیاء آشنا می‌شویم.