تخصیص حافظه، ارجاع و مفهوم Reachability در جاوااسکریپت

در طول این دوره، هر بار که متغیری تعریف یا شیئی ساخته‌اید، جاوااسکریپت در پس‌زمینه بخشی از حافظه‌ی سیستم را به آن اختصاص داده است. برخلاف زبان‌هایی مثل C که مدیریت این حافظه کاملاً بر عهده‌ی برنامه‌نویس است، جاوااسکریپت این فرآیند را به‌طور خودکار مدیریت می‌کند. اما درک درست این مکانیزم—چگونگی تخصیص حافظه، معنای واقعی یک ارجاع (reference)، و مفهوم Reachability—پیش‌نیاز مستقیم فهم Garbage Collection است که در درس بعدی بررسی می‌شود.

تخصیص حافظه هنگام ساخت مقادیر

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

let count = 42;                  // تخصیص حافظه برای یک عدد
let name = "سارا";                // تخصیص حافظه برای یک رشته
let user = { id: 1, name: "علی" }; // تخصیص حافظه برای یک شیء

نکته‌ی مهم این است که مقادیر primitive مثل عدد و رشته، و مقادیر شیئی مثل object و آرایه، از نظر نحوه‌ی ذخیره در حافظه رفتار متفاوتی دارند که در ادامه بررسی می‌شود.

تفاوت ذخیره‌ی مقدار در برابر ذخیره‌ی ارجاع

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

تفاوت رفتار primitive و شیء هنگام نسبت‌دهی
خروجی کنسول اینجا نمایش داده می‌شود...

این رفتار دقیقاً همان چیزی است که در فصل‌های اولیه‌ی دوره، هنگام بحث درباره‌ی تفاوت انواع primitive و object دیده بودید؛ اما در این‌جا از منظر واقعی مدیریت حافظه بررسی می‌شود.

Reachability: معیار اصلی نگهداری داده در حافظه

موتور جاوااسکریپت داده‌ای را در حافظه نگه می‌دارد تا زمانی که آن داده قابل دسترسی (reachable) باشد؛ یعنی راهی برای رسیدن به آن، مستقیم یا غیرمستقیم، از یکی از ریشه‌های اصلی برنامه (مثل متغیرهای سراسری یا متغیرهای محلی توابع در حال اجرا) وجود داشته باشد:

let user = { name: "سارا" }; // این شیء reachable است، چون متغیر user به آن اشاره دارد

user = null; // اکنون هیچ ارجاعی به شیء { name: "سارا" } وجود ندارد
// این شیء دیگر reachable نیست و برای پاک‌سازی آماده می‌شود

Reachability غیرمستقیم از طریق زنجیره‌ی ارجاع

یک داده لازم نیست مستقیماً توسط یک متغیر ریشه نگه داشته شود؛ کافی است از طریق زنجیره‌ای از ارجاعات به آن رسید:

function createTeam() {
  const leader = { name: "مدیر تیم" };
  const team = { members: [leader] };
  return team;
}

const myTeam = createTeam();
// leader دیگر مستقیماً توسط هیچ متغیری نگه داشته نمی‌شود،
// اما همچنان reachable است، چون myTeam.members[0] به آن اشاره دارد

در این مثال، حتی پس از پایان اجرای تابع createTeam، شیء leader reachable باقی می‌ماند، چون از طریق زنجیره‌ی myTeammembersleader همچنان قابل دسترسی است.

وقتی زنجیره‌ی ارجاع قطع می‌شود

اگر تمام مسیرهای رسیدن به یک شیء از ریشه‌ها قطع شوند، آن شیء غیرقابل‌دسترسی (unreachable) می‌شود، حتی اگر خود شیء همچنان در جایی از حافظه فیزیکی باقی مانده باشد:

let myTeam = createTeam();
myTeam = null;
// اکنون هم team و هم leام (که از طریق team.members[0] در دسترس بود)،
// هر دو unreachable شده‌اند

جدول مقایسه‌ی رفتار primitive و object در حافظه

ویژگی مقدار primitive مقدار شیئی (object/array)
نسبت‌دهی به متغیر دیگر کپی کامل مقدار اشتراک همان مکان حافظه (ارجاع)
تغییر از طریق یک متغیر روی متغیرهای دیگر اثر ندارد روی تمام متغیرهایی که همان ارجاع را دارند اثر می‌گذارد

نکات کلیدی

  • هر مقدار جدید در جاوااسکریپت، به‌طور خودکار حافظه‌ی لازم برای خود را دریافت می‌کند.
  • نسبت‌دهی یک مقدار primitive، یک کپی کامل می‌سازد؛ نسبت‌دهی یک شیء، فقط یک ارجاع جدید به همان مکان حافظه ایجاد می‌کند.
  • Reachability معیار اصلی جاوااسکریپت برای تصمیم‌گیری درباره‌ی نگهداری یا آزادسازی حافظه‌ی یک مقدار است.
  • یک شیء حتی بدون ارجاع مستقیم از یک متغیر ریشه، تا زمانی که از طریق زنجیره‌ای از ارجاعات قابل دسترسی باشد، reachable باقی می‌ماند.

جمع‌بندی

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