في TypeScript ثلاثة معدّلات وصول لأعضاء الصنف: public (الافتراضي)، وprotected، وprivate. تتحكم في المواضع التي يمكن فيها استخدام العضو، ويبلّغ المترجم عن أي وصول من الموضع الخاطئ.
السطر الأخير خطأ ترجمة، لكن انظر إلى ما طبعه حين شُغّل رغم ذلك: 123-45-6789. هذه أهم حقيقة في هذه الصفحة، ويشرحها قسم private.
ما يسمح به كل معدّل
| المعدّل | داخل الصنف | في صنف فرعي | من الخارج | يُفرض وقت التشغيل |
|---|---|---|---|---|
public (الافتراضي) | نعم | نعم | نعم | لا شيء يُفرض |
protected | نعم | نعم | لا | لا |
private | نعم | لا | لا | لا |
#name (JavaScript) | نعم | لا | لا | نعم |
readonly | قراءة، وكتابة في دالة البناء | قراءة | قراءة | لا |
تعمل المعدّلات على الحقول والتوابع والـ getters والـ setters ودوال البناء وخصائص المعاملات (constructor(private id: string)). كتابة public اختيارية، وكثير من المشاريع تحذفها.
private فحص وقت الترجمة
تحذف TypeScript المعدّل private مع بقية الأنواع. الصنف المترجم يحتوي على خاصية عادية، فيراها كل ما لا يمر عبر فاحص الأنواع: مستدعو JavaScript العاديون، وJSON.stringify، وObject.keys، بل وصيغة الأقواس المربعة في TypeScript نفسها، المسموحة كمخرج مقصود.
هذا مناسب للغرض الذي وُجد له private: إخبار المطورين الآخرين (ومحررك) بأن العضو تفصيل تنفيذي. إنه ليس حدًا أمنيًا، وسينتهي الحقل private في السجلات واستجابات JSON ما لم تحذفه بنفسك.
حقول #private: تُفرض وقت التشغيل
لـ JavaScript حقولها الخاصة، وتُكتب بـ #. يفرضها المحرك: خارج الصنف لا تكون obj.#field صيغة صالحة أصلًا، ولا يظهر الحقل في Object.keys ولا JSON.stringify ولا console.log.
كتابة s.#token خارج الصنف هي الخطأ TS18013 (Property '#token' is not accessible outside class 'Session' because it has a private identifier)، وعلى خلاف حالة private لا توجد طريقة للالتفاف عليها وقت التشغيل أيضًا. راجع الحقول الخاصة في JavaScript لقواعد وقت التشغيل.
private مقابل #private: أيهما تستخدم
private x | #x | |
|---|---|---|
| يفحصه | المترجم | محرك JavaScript |
مرئي لـ Object.keys / JSON.stringify | نعم | لا |
الوصول بالأقواس obj["x"] | مسموح | غير ممكن |
يمكن للصنف الفرعي تعريف x خاص به | لا، يتعارض | نعم، لكل صنف #x خاص به |
يُنسخ بالنشر { ...obj } | نعم | لا |
| الصيغة على التوابع | private helper() | #helper() |
استخدم #private حين يجب أن تبقى البيانات خاصة وقت التشغيل (الرموز المميزة، والحالة الداخلية التي لا يجوز لمستخدمي مكتبة لمسها)، أو حين تريد أن يتجاهلها JSON.stringify. واستخدم private حين يكون الهدف مجرد واجهة عامة نظيفة، أو حين يحتاج إطار عمل إلى قراءة الحقل بالانعكاس (reflection)، أو لمطابقة الأسلوب القائم في المشروع. لا تجمع بينهما: private #x هو الخطأ TS18010 (An accessibility modifier cannot be used with a private identifier).
protected والأصناف الفرعية
العضو protected متاح داخل الصنف وفي أي صنف يرث منه، لكن ليس على النسخ من الخارج.
قاعدة واحدة تفاجئ الناس. داخل Polygon يمكنك قراءة sides على this أو على Polygon آخر، لكن ليس على Shape عادي يُمرَّر كمعامل: other.sides حيث other: Shape هو الخطأ TS2446 (Property 'sides' is protected and only accessible through an instance of class 'Polygon'. This is an instance of class 'Shape'.). لا يصل الصنف الفرعي إلى الأعضاء المحمية إلا في الكائنات التي تنتمي إلى فرعه من الهرمية.
يمكن للصنف الفرعي أن يجعل عضوًا محميًا عامًا بإعادة تعريفه، لكنه لا يستطيع أن يجعل عضوًا عامًا محميًا أو خاصًا. أعد تعريفه بقيمة ابتدائية (public override sides = 6) أو كنوع فقط (declare public sides: number). أما public sides: number; المجرد فيُرفض بالخطأين TS2564 وTS2612، لأنه مع حقول الأصناف الحديثة سيعيد ضبط القيمة الموروثة إلى undefined بعد عودة super().
readonly
يمنع readonly إعادة الإسناد بعد الإنشاء. يمكن ضبط الحقل في تعريفه أو في دالة البناء؛ وأي إسناد لاحق هو الخطأ TS2540.
يظهر هنا حدّان. readonly سطحي: لا يمكن استبدال المصفوفة، لكن محتواها يمكن أن يتغير (اجعل نوعها readonly string[] لمنع push). ومثل private يختفي وقت التشغيل، فالإسناد الذي رفضه المترجم نُفّذ رغم ذلك. للقيمة التي يجب ألا تتغير وقت التشغيل استخدم Object.freeze أو getter بلا setter. تشرح صفحة readonly النوع Readonly<T> والمصفوفات للقراءة فقط.
يجتمع readonly مع معدّلات الوصول: private readonly cache = new Map<string, number>() نمط شائع لحقل داخلي لا يُعاد إسناده أبدًا.
أخطاء شائعة
- معاملة
privateكأداة أمان. يختفي وقت التشغيل. استخدم#fieldللبيانات التي يجب أن تبقى مخفية، ولا ترسل أبدًا كائنًا يحمل أسرارًا مباشرة إلىJSON.stringify. - كتابة
publicفي كل مكان. إنه الافتراضي؛ وإضافته لا تغيّر شيئًا. - استخدام
protectedلكل ما هو «داخلي». إن لم يحتجه أي صنف فرعي، فإنprivateيُبقي الواجهة أصغر. - توقّع أن يجمّد
readonlyالبيانات المتداخلة. إنه يمنع فقط إعادة إسناد الخاصية نفسها.
الأسئلة الشائعة
ما هي معدّلات الوصول في TypeScript؟
public (الافتراضي: متاح في كل مكان)، وprotected (داخل الصنف وأصنافه الفرعية)، وprivate (داخل الصنف فقط). أما readonly فمعدّل مستقل يمنع إعادة الإسناد بعد دالة البناء، ويمكن جمعه مع أي من الثلاثة.
ما الفرق بين private و#private في TypeScript؟
private يفحصه المترجم فقط؛ فيحتوي JavaScript الناتج على خاصية عادية، وتراها obj["secret"] وJSON.stringify وObject.keys. أما #secret فحقل خاص في JavaScript: يفرضه وقت التشغيل، ولا يستطيع الكود خارج الصنف قراءته إطلاقًا.
هل private في TypeScript خاص فعلًا؟
وقت الترجمة فقط. بعد الترجمة يصبح الحقل خاصية عادية يستطيع أي كود JavaScript قراءتها. بل إن TypeScript تسمح بالوصول بالأقواس المربعة (obj["field"]) إلى الأعضاء الخاصة كمخرج مقصود. استخدم #field حين يجب أن تصمد الخصوصية وقت التشغيل.
ما الفرق بين protected وprivate في TypeScript؟
العضو private مرئي فقط داخل الصنف الذي يعرّفه. أما العضو protected فمرئي أيضًا داخل الأصناف الفرعية. ولا يمكن الوصول إلى أي منهما على نسخة من خارج هرمية الأصناف.
هل يمكن تغيير خصائص readonly في TypeScript؟
يمكن إسناد خاصية readonly في تعريفها أو في دالة البناء، ولا مكان غير ذلك (وإلا فالخطأ TS2540). وهي سطحية: لا يمكن استبدال readonly tags: string[]، لكن tags.push() لا تزال تعمل. استخدم readonly string[] لمنع ذلك أيضًا.