ב-TypeScript יש שלושה מגדירי גישה לאיברי מחלקה: public (ברירת המחדל), protected ו-private. הם קובעים איפה אפשר להשתמש באיבר, והקומפיילר מדווח על כל גישה מהמקום הלא נכון.
השורה האחרונה היא שגיאת קומפילציה, אבל שימו לב מה היא הדפיסה כשהקוד הורץ בכל זאת: 123-45-6789. זו העובדה החשובה ביותר בעמוד הזה, והחלק על private מסביר אותה.
מה כל מגדיר מאפשר
| מגדיר | בתוך המחלקה | בתת-מחלקה | מבחוץ | נאכף בזמן ריצה |
|---|---|---|---|---|
public (ברירת מחדל) | כן | כן | כן | אין מה לאכוף |
protected | כן | כן | לא | לא |
private | כן | לא | לא | לא |
#name (JavaScript) | כן | לא | לא | כן |
readonly | קריאה, וכתיבה בבנאי | קריאה | קריאה | לא |
המגדירים עובדים על שדות, מתודות, getters ו-setters, בנאים ו-parameter properties (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 משלה |
מועתק ב-spread { ...obj } | כן | לא |
| תחביר במתודות | private helper() | #helper() |
השתמשו ב-#private כשהמידע חייב להישאר פרטי בזמן ריצה (טוקנים, מצב פנימי שמשתמשי ספרייה לא אמורים לגעת בו) או כשאתם רוצים ש-JSON.stringify ישמיט אותו. השתמשו ב-private כשהמטרה היא רק API ציבורי נקי, כש-framework צריך לקרוא את השדה בצורה רפלקטיבית, או כדי להתאים לסגנון הקיים של בסיס הקוד. אל תשלבו ביניהם: 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, כי עם class fields מודרניים הוא היה מאפס את הערך שנורש ל-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[] כדי לחסום גם את זה.