انواع ارجاعی داده در جاوااسکریپت

اهداف یادگیری

  • شناخت Reference Types و تفاوت آن‌ها با Primitive Types
  • درک این‌که متغیرهای Reference Type چگونه در حافظه ذخیره می‌شوند
  • فهم رفتار کپی شدن ارجاع به‌جای کپی شدن داده
  • توانایی پیش‌بینی رفتار کد هنگام مقایسه یا انتقال Object و Array

مقدمه

در درس قبل دیدیم که Primitive Types مقادیری مستقل و Immutable هستند که هنگام کپی شدن، به‌طور کامل تکثیر می‌شوند. اما دسته‌ی دوم انواع داده در جاوااسکریپت، یعنی Reference Types، رفتار کاملاً متفاوتی دارند. Object، Array و Function همگی از این دسته‌اند.

Reference Types در جاوااسکریپت

رایج‌ترین Reference Types عبارت‌اند از:

  • Object — مجموعه‌ای از Propertyهای کلید-مقدار
  • Array — لیستی ترتیبی از مقادیر
  • Function — که خود نوعی شیء خاص در جاوااسکریپت است
let user = { name: "علی", age: 25 };
let numbers = [1, 2, 3];
let greet = function () {
  console.log("سلام");
};

تفاوت اصلی: ارجاع در برابر مقدار

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

let user1 = { name: "علی" };
let user2 = user1; // فقط ارجاع کپی می‌شود

user2.name = "رضا";

console.log(user1.name); // رضا
console.log(user2.name); // رضا

تحلیل کد

در مثال بالا، تغییر user2.name باعث تغییر user1.name نیز می‌شود؛ چون user1 و user2 هر دو به یک شیء در حافظه اشاره می‌کنند، نه به دو شیء جداگانه. این دقیقاً همان نکته‌ای است که در درس قبل درباره‌ی const اشاره شد: منعِ const فقط جلوی تغییرِ خودِ ارجاع را می‌گیرد، نه محتوای شیء را.

مقایسه‌ی Reference Types

یکی از پیامدهای مهم این رفتار، نحوه‌ی مقایسه‌ی دو شیء یا آرایه است:

let a = { name: "علی" };
let b = { name: "علی" };
let c = a;

console.log(a === b); // false — دو شیء جدا در حافظه‌اند
console.log(a === c); // true — هر دو به یک شیء اشاره دارند

حتی اگر محتوای دو شیء کاملاً یکسان باشد، مقایسه‌ی آن‌ها با === مقدار false برمی‌گرداند؛ چون این عملگر ارجاع را مقایسه می‌کند، نه محتوای داخلی را.

کپی واقعی یک شیء یا آرایه

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

let original = { name: "علی" };
let copy = { ...original };

copy.name = "رضا";

console.log(original.name); // علی — بدون تغییر
console.log(copy.name);     // رضا

جزئیات کامل Spread Operator و کاربردهای آن را در فصل‌های مربوط به آرایه و شیء بررسی خواهیم کرد. نکته‌ای که در این مرحله باید بدانید این است که این کپی، تنها یک سطح (Shallow Copy) را کپی می‌کند؛ اگر شیء شامل شیء یا آرایه‌ی دیگری در دل خود باشد، آن بخش داخلی همچنان به‌صورت ارجاع مشترک باقی می‌ماند.

جدول مقایسه‌ی Primitive و Reference

ویژگیPrimitive TypesReference Types
نحوه‌ی ذخیرهمقدار مستقیمارجاع به حافظه
هنگام کپی شدنمقدار تکثیر می‌شودارجاع کپی می‌شود
Mutable بودنخیر (Immutable)بله (Mutable)
مقایسه با ===مقدار را مقایسه می‌کندارجاع را مقایسه می‌کند

اشتباهات رایج

  • تصور اینکه let copy = original یک کپی مستقل می‌سازد؛ درحالی‌که فقط ارجاع کپی می‌شود.
  • انتظار داشتن این‌که مقایسه‌ی دو شیء با محتوای یکسان توسط === مقدار true برگرداند.
  • فراموش کردن این‌که Spread Operator فقط یک Shallow Copy انجام می‌دهد.

Best Practices

  • هنگام نیاز به یک کپی مستقل از شیء یا آرایه، همیشه از روش‌های صریح کپی‌سازی استفاده کنید، نه انتساب ساده.
  • هنگام مقایسه‌ی دو شیء یا آرایه از نظر محتوا (نه ارجاع)، باید Propertyهای آن‌ها را جداگانه مقایسه کرد، نه با ===.

ارتباط با سایر مفاهیم

درک رفتار Reference Types، پایه‌ی فهم صحیح Closure، نحوه‌ی عملکرد پارامترهای تابع، و همچنین مدیریت حافظه (Memory) در فصل‌های پیشرفته‌تر این دوره خواهد بود.

جمع‌بندی

برخلاف Primitive Types، مقادیر Reference Type مانند Object، Array و Function به‌صورت ارجاع در حافظه نگهداری می‌شوند. کپی کردن یک متغیر Reference Type، ارجاع را کپی می‌کند نه محتوا را؛ به همین دلیل تغییر از طریق یک متغیر، در متغیر دیگری که به همان شیء اشاره دارد نیز قابل مشاهده است. این تفاوت اساسی، در تمام مراحل بعدی این دوره تأثیرگذار خواهد بود.