محدود کردن دسترسی و اشتراک‌گذاری سطح کلاس: Private Fields و Static Members

مقدمه

در فصل Closure، دیدیم که چگونه می‌توان با استفاده از Module Pattern، حالت خصوصی (Private State) ساخت که از بیرون شیء قابل دسترسی مستقیم نباشد. Class در جاوااسکریپت مدرن، یک راه‌حل رسمی و داخل خودِ زبان برای همین مسئله ارائه می‌دهد: Private Fields. در کنار آن، با مفهوم Static Members هم آشنا می‌شویم؛ اعضایی که به خودِ Class تعلق دارند، نه به هیچ Instance خاصی.

مشکل: Property های عادی همیشه Public هستند

تمام Property هایی که تا اینجا در Class دیدیم، از بیرون کاملاً قابل دسترسی و تغییر هستند:

class BankAccount {
  constructor(initialBalance) {
    this.balance = initialBalance;
  }
}

let account = new BankAccount(1000);
account.balance = -5000; // هیچ مانعی وجود ندارد
console.log(account.balance); // -5000

این دقیقاً همان مشکلی است که در فصل Closure، هنگام معرفی Private State، دیدیم.

Private Fields — راه‌حل رسمی زبان

با قرار دادن علامت # پیش از نام یک Property، آن Property به یک Private Field تبدیل می‌شود؛ فیلدی که فقط از داخل خودِ Class قابل دسترسی است، نه از بیرون:

تعریف و استفاده از Private Field
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل کد

در این مثال، #balance فقط از داخل متدهای همان Class (مثل deposit و getBalance) قابل خواندن یا نوشتن است. تلاش برای دسترسی مستقیم از بیرون، مثل account.balance (بدون #)، هرگز به همان فیلد Private متصل نمی‌شود؛ فقط undefined برمی‌گرداند، چون balance (بدون #) اصلاً روی این شیء تعریف نشده است.

اگر مستقیماً تلاش کنیم با نحو # از بیرون Class به آن دسترسی پیدا کنیم (مثل account.#balance)، جاوااسکریپت حتی خطای Syntax صادر می‌کند؛ این محدودیت بسیار سخت‌گیرانه‌تر از راه‌حل Closure در فصل قبل است که در آن‌جا، حالت خصوصی صرفاً از طریق قرارداد و ساختار کد مخفی می‌ماند، نه یک قانون رسمی زبان.

تفاوت Private Field با راه‌حل Closure

ویژگیModule Pattern (Closure)Private Fields (#)
پشتیبانی رسمی زبانخیر (فقط قرارداد کد)بله
نیاز به return یک شیء خاصبلهخیر، مستقیماً در Class
قابل استفاده در Classغیرمستقیم و پیچیدهمستقیم و ساده

Private متدها

همین # روی متدها هم قابل استفاده است تا متدهای کمکی داخلی، از بیرون Class قابل فراخوانی نباشند:

class BankAccount {
  #balance;

  constructor(initialBalance) {
    this.#balance = initialBalance;
  }

  #validateAmount(amount) {
    return amount > 0;
  }

  deposit(amount) {
    if (this.#validateAmount(amount)) {
      this.#balance += amount;
    }
  }
}

#validateAmount فقط از داخل خودِ Class (مثلاً از داخل deposit) قابل فراخوانی است؛ هیچ کد بیرونی نمی‌تواند مستقیماً account.#validateAmount(10) را صدا بزند.

Static Members — اعضایی متعلق به خودِ Class

تا اینجا هر Property و متدی که دیدیم، به یک Instance خاص تعلق داشت (یا مستقیم روی this، یا از طریق Prototype به اشتراک گذاشته می‌شد). اما گاهی نیاز داریم Property یا متدی داشته باشیم که به خودِ Class تعلق دارد، نه به هیچ Instance خاصی. برای این منظور از کلیدواژه‌ی static استفاده می‌کنیم:

تعریف Static Property و Static Method
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل رفتار جاوااسکریپت

User.count یک Property است که مستقیماً روی خودِ Class (نه روی هیچ Instance ای) قرار دارد؛ هر بار که یک Instance جدید ساخته می‌شود، این مقدار مشترک افزایش پیدا می‌کند. متد getUserCount نیز یک Static Method است؛ فقط از طریق خودِ Class، یعنی User.getUserCount()، قابل فراخوانی است، نه از طریق یک Instance. به همین دلیل user1.getUserCount مقدار undefined برمی‌گرداند؛ این متد اصلاً روی User.prototype قرار نگرفته، بلکه مستقیماً روی خودِ تابع User تعریف شده است.

چرا Static Members مفید هستند؟

Static Members برای داده‌ها یا عملیاتی مناسب هستند که مفهوماً به «کل دسته» مربوط می‌شوند، نه به یک عضو خاص از آن دسته؛ مثل شمارش تعداد کل Instance های ساخته‌شده (مثال بالا)، یا متدهای کمکی که به‌طور مستقل از هر Instance خاصی عمل می‌کنند، مثل یک متد اعتبارسنجی عمومی یا یک متد Factory برای ساخت Instance با تنظیمات خاص.

class User {
  constructor(name) {
    this.name = name;
  }

  static createGuest() {
    return new User("مهمان");
  }
}

let guest = User.createGuest();
console.log(guest.name); // مهمان

Private Static Fields

می‌توان # و static را با هم ترکیب کرد تا یک Property داشته باشیم که هم متعلق به خودِ Class باشد و هم فقط از داخل Class قابل دسترسی:

class User {
  static #totalUsers = 0;

  constructor(name) {
    this.name = name;
    User.#totalUsers++;
  }

  static getTotalUsers() {
    return User.#totalUsers;
  }
}

console.log(User.getTotalUsers()); // 0
new User("علی");
console.log(User.getTotalUsers()); // 1

جدول جمع‌بندی

مفهومتعلق دارد بهقابل دسترسی از
Property/Method عادیهر Instance به‌طور جداگانههمه‌جا (Public)
Private Field (#field)هر Instance به‌طور جداگانهفقط داخل خودِ Class
Static Memberخودِ Classفقط از طریق نام Class
Private Static (#field static)خودِ Classفقط داخل خودِ Class، از طریق نام Class

جمع‌بندی

Private Fields، با استفاده از علامت # پیش از نام Property یا متد، راه‌حل رسمی زبان جاوااسکریپت برای ساخت حالت واقعاً خصوصی هستند؛ چیزی که پیش از این، فقط با تکنیک‌های غیررسمی مثل Module Pattern مبتنی بر Closure شبیه‌سازی می‌شد. Static Members، با استفاده از کلیدواژه‌ی static، Property ها و متدهایی می‌سازند که به خودِ Class تعلق دارند، نه به هیچ Instance خاصی، و برای داده یا عملیات مشترک در سطح کل دسته مناسب هستند.