محدود کردن دسترسی و اشتراکگذاری سطح کلاس: 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 قابل دسترسی است، نه از بیرون:
تحلیل کد
در این مثال، #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 استفاده میکنیم:
تحلیل رفتار جاوااسکریپت
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 خاصی، و برای داده یا عملیات مشترک در سطح کل دسته مناسب هستند.
