class - מהם ההבדלים בין המילת מפתח private לשדות private ב-TypeScript?
ב- TypeScript 3.8 ומעלה, מה ההבדלים בין שימוש במילת המפתח private כדי לסמן חבר כפרטי:
class PrivateKeywordClass {
private value = 1;
}
ושימוש בשדות פרטיים # שהוצעו עבור JavaScript:
class PrivateFieldClass {
#value = 1;
}
האם עלי להעדיף אחד על פני השני?
תשובה #1
מילת מפתח פרטית
המילה השמורה private ב-TypeScript היא הערת זמן קומפילציה. היא אומרת למקמפל שפרופרטי צריך להיות נגיש רק בתוך המחלקה הזו:
class PrivateKeywordClass {
private value = 1;
}
const obj = new PrivateKeywordClass();
obj.value // compiler error: Property 'value' is private and only accessible within class 'PrivateKeywordClass'.
עם זאת, בדיקת זמן ההידור יכולה להיות מעוקפת בקלות, לדוגמה על ידי הסרת מידע הסוג:
const obj = new PrivateKeywordClass();
(obj as any).value // no compile error
המילת מפתח private גם אינה נאכפת בזמן ריצה.
JavaScript שנפלט
כאשר מכינים קוד TypeScript ל-JavaScript, המילה השמורה private פשוט מוסרת:
class PrivateKeywordClass {
private value = 1;
}
נהיה :
class PrivateKeywordClass {
constructor() {
this.value = 1;
}
}
מכאן אפשר להבין מדוע מילת המפתח הפרטית אינה מספקת הגנה בזמן ריצה: ב-JavaScript שנוצר זו פשוט תכונת JavaScript רגילה.
שדות פרטיים
שדות פרטיים מבטיחים שהמאפיינים יישארו פרטיים בזמן ריצה:
class PrivateFieldClass {
#value = 1;
getValue() { return this.#value; }
}
const obj = new PrivateFieldClass();
// You can't access '#value' outside of class like this
obj.value === undefined // This is not the field you are looking for.
obj.getValue() === 1 // But the class itself can access the private field!
// Meanwhile, using a private field outside a class is a runtime syntax error:
obj.#value
// While trying to access the private fields of another class is
// a runtime type error:
class Other {
#value;
getValue(obj) {
return obj.#value // TypeError: Read of private field #value from an object which did not contain the field
}
}
new Other().getValue(new PrivateKeywordClass());
TypeScript גם תפיק שגיאת קומפילציה אם תנסה להשתמש בשדה פרטי מחוץ למחלקה:
שדות פרטיים משתנים מהצעת TC-39 ל-ECMAScript והם חלק ממפרט ECMAScript של 2021, מה שאומר שניתן להשתמש בהם גם ב-JavaScript רגיל וגם ב-TypeScript.
JavaScript שנפלט
אם אתה משתמש בשדות פרטיים ב-TypeScript ומייעד את הפלט שלך ל-ES2021 או לגרסאות ישנות יותר של JavaScript, TypeScript יוצר קוד שמחקה את התנהגות הריצה של השדות הפרטיים באמצעות WeakMap (מקור).
class PrivateFieldClass {
constructor() {
_x.set(this, 1);
}
}
_x = new WeakMap();
אם אתה מכוון לכל דבר אחרי ES2021, TypeScript תפיק את השדה הפרטי:
class PrivateFieldClass {
constructor() {
this.#x = 1;
}
#x;
}
באילו מהם עלי להשתמש?
זה תלוי במה שאתה מנסה להשיג.
מילת המפתח private היא ברירת מחדל טובה. היא משיגה את מה שהיא תוכננה להשיג והייתה בשימוש מוצלח על ידי מפתחי TypeScript במשך שנים. ואם יש לכם בסיס קוד קיים, אינכם צריכים להמיר את כל הקוד שלכם לשימוש בשדות פרטיים. זה נכון במיוחד אם אינכם מתמקדים ב-esnext, משום שה-JS ש-TypeScript מפיק עבור שדות פרטיים עשוי להשפיע על הביצועים. כמו כן, קחו בחשבון שלשדות פרטיים יש הבדלים דקים אך חשובים נוספים לעומת מילת המפתח private.
עם זאת, אם אתה צריך לאכוף פרטיות בזמן ריצה או מייצא JavaScript בגירסת esnext, אז עליך להשתמש בשדות פרטיים.
כמו כן שים לב שהמנהגים של ארגונים/קהילות לגבי השימוש באחד או השני גם הם יתפתחו ככל ששדות פרטיים יהפכו לנפוצים יותר בתוך האקוסיסטמות של JavaScript/TypeScript.
הבדלים נוספים לציון
שדות פרטיים אינם מוחזרים על ידי
Object.getOwnPropertyNamesושיטות דומותשדות פרטיים אינם ממוינים על ידי
JSON.stringifyיש מקרים חשובים במיוחד סביב ירושה
TypeScript, למשל, אוסרת על הכרזת שדה פרטי בתת-מחלקה עם אותו שם כמו שדה פרטי במחלקה העליונה.
class Base { private value = 1; } class Sub extends Base { private value = 2; // Compile error: }זוהי לא אמת לגבי שדות פרטיים:
class Base { #value = 1; } class Sub extends Base { #value = 2; // Not an error }
מילת המפתח
privateעבור פרטי ללא מאותחל לא תיצור הכרזת משתנה בג'אווהסקריפט שנפלט:class PrivateKeywordClass { private value?: string; getValue() { return this.value; } }מתורגם ל:
class PrivateKeywordClass { getValue() { return this.value; } }ובזמן ששדות פרטיים תמיד יוצרים הצהרת נכס:
class PrivateKeywordClass { #value?: string; getValue() { return this.#value; } }מתקמפל ל-(כאשר מכוון ל-
esnext):class PrivateKeywordClass { #value; getValue() { return this.#value; } }
להמשך קריאה:
- העתיד של המילה השמורה "private"
- בקשת משיכה ב-TypeScript שהוסיפה שדות פרטיים
תשובה #2
מקרי שימוש: #-שדות פרטיים
מבוא :
- הצעת TC39 לשדות מחלקה
- מונחים נרדפים:
#-פרטי, פרטי קשוח, פרטי בזמן ריצה
פרטיות בזמן קומפילציה ו בזמן ריצה
שדות פרטיים # מספקים פרטיות בזמן קומפילציה ובזמן ריצה, שאינה "ניתנת לפריצה". זהו מנגנון למניעת גישה לחבר מתוך מחלקה מבחוץ בכל דרך ישירה.
class A {
#a: number;
constructor(a: number) {
this.#a = a;
}
}
let foo: A = new A(42);
foo.#a; // error, not allowed outside class bodies
(foo as any).#bar; // still nope.
ירושה בטוחה של מחלקה
שדות פרטיים עם # מקבלים תחום ייחודי. ניתן לממש היררכיות מחלקות מבלי שמאפיינים פרטיים עם שמות זהים יכתבו בטעות.
class A {
#a = "a";
fnA() { return this.#a; }
}
class B extends A {
#a = "b";
fnB() { return this.#a; }
}
const b = new B();
b.fnA(); // returns "a" ; unique property #a in A is still retained
b.fnB(); // returns "b"
למזלנו, קומפיילר TypeScript מפיק שגיאה כאשר נכסים private עלולים להימחק (ראו את הדוגמה הזו). אבל בגלל הטבע של תכונת זמן קומפילציה, הכל עדיין אפשרי בזמן ריצה, אם שגיאות הקומפילציה מתעלמים מהן ו/או הקוד המוכלל ב-JS מנוצל.
ספריות חיצוניות
מחברי ספריות יכולים לבצע רפקטורינג למזהים פרטיים שמתחילים ב-# מבלי לגרום לשבירת קוד עבור הלקוחות. משתמשי הספרייה מהצד השני מוגנים מפני גישה לשדות פנימיים.
ממשק API של JS מתעלם משדות פרטיים #
פונקציות ושיטות מובנות ב-JS מתעלמות משדות פרטיים #. הדבר יכול להוביל לבחירת מאפיינים צפויה יותר בזמן ריצה. דוגמאות: Object.keys, Object.entries, JSON.stringify, לולאת for..in ואחרות (דוגמת קוד; ראו גם את תשובתו של מאט בירנר):
class Foo {
#bar = 42;
baz = "huhu";
}
Object.keys(new Foo()); // [ "baz" ]
מקרי שימוש: מילת המפתח private
מבוא :
- מילת המפתח
privateבתיעוד של TS - מונחים נרדפים: פרטי TS, פרטי רך, פרטי בזמן קומפילציה
גישה ל-API הפנימי של המחלקה ולמצב (פרטיות בזמן קומפילציה בלבד)
החברים private של מחלקה הם תכונות קונבנציונליות בזמן ריצה. אנו יכולים להשתמש בגמישות זו כדי לגשת ל-API הפנימי של המחלקה או למצב שלה מבחוץ. כדי לעמוד בבדיקות הקומפיילר, ניתן להשתמש במנגנונים כמו הבטחות טיפוס (type assertions), גישה דינמית לתכונות או @ts-ignore בין היתר.
דוגמה עם הצהרת טיפוס (as/<>) והקצאת משתנה עם טיפוס any:
class A {
constructor(private a: number) { }
}
const a = new A(10);
a.a; // TS compile error
(a as any).a; // works
const casted: any = a; casted.a // works
TypeScript אפילו מאפשר גישה דינמית לנכס פרטי בעזרת מנגנון מילוט:
class C {
private foo = 10;
}
const res = new C()["foo"]; // 10, res has type number
באילו מקרים גישה פרטית יכולה להיות הגיונית? (1) בדיקות יחידה, (2) מצבי ניפוי/רישום או (3) תרחישי מקרה מתקדמים אחרים עם מחלקות פנימיות לפרויקט (רשימה פתוחה).
גישה למשתנים פנימיים היא מעט סותרת - אחרת לא היית עושה אותם private מלכתחילה. כדוגמה, מבחני יחידה אמורים להיות תיבות שחורות/אפורות עם שדות פרטיים המוסתרים כפרטי מימוש. בפועל, עם זאת, עשויות להיות גישות חוקיות ממקרה למקרה.
זמין בכל סביבות ES
ניתן להשתמש במודיפיירים private של TypeScript עם כל היעדים של ES. שדות #-private זמינים רק עבור target ES2015/ES6 או גבוה יותר. ב-ES6+, נעשה שימוש בתוך WeakMap כמימוש ברמת נמוכה (ראה כאן). שדות מקוריים #-private כרגע דורשים target esnext.
עקביות ותאימות
צוותים עשויים להשתמש בקווי הנחיה לקידוד וכללי לינט כדי לאכוף את השימוש ב-private כמפתח הגישה היחידי. הגבלה זו יכולה לסייע בעקביות ולמנוע בלבול עם הסימון # של שדות פרטיים בצורה שתואמת לאחור.
אם נדרש, תכונות פרמטר (קיצור השמה בבונה) הן חוסמות הופעה. ניתן להשתמש בהן רק עם מילת המפתח private ואין עדיין תוכניות ליישם אותן עבור שדות פרטיים #.
סיבות אחרות
privateעשוי לספק ביצועי ריצה טובים יותר בחלק מהמקרים של הורדה לרמות נמוכות יותר (ראה כאן).- אין שיטות מחלקה פרטיות קשות זמינות ב-TS עד עכשיו.
- יש אנשים שמעדיפים את הסימון של מילת המפתח
privateיותר 😊.
הערה על שניהם
שתי הגישות יוצרות סוג כלשהו נומינלי או ממותג בזמן הידור.
class A1 { private a = 0; }
class A2 { private a = 42; }
const a: A1 = new A2();
// error: "separate declarations of a private property 'a'"
// same with hard private fields
בנוסף, שני אלה מאפשרים גישה בין מופעים: מופע של המחלקה A יכול לגשת לחברים פרטיים של מופעים אחרים של A:
class A {
private a = 0;
method(arg: A) {
console.log(arg.a); // works
}
}
מקורות
- https://devblogs.microsoft.com/typescript/announcing-typescript-3-8-beta/#ecmascript-private-fields
- https://github.com/microsoft/TypeScript/issues/31670
- https://github.com/Microsoft/TypeScript/pull/30829
