Menu

ירושה ב-JavaScript: extends, super ודריסת מתודות

איך ירושה עובדת במחלקות של JavaScript: extends, super, דריסת מתודות, ומתי ירושה היא הכלי הלא נכון.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

מחלקה אחת שנבנית על אחרת

ירושה מאפשרת למחלקה להתחיל מהשרטוט של מחלקה אחרת ולהרחיב אותו. מקבלים בחינם כל שדה ומתודה של מחלקת האב, ואפשר להוסיף או לשנות כל מה שצריך:

Dog extends Animal אומר: "Dog הוא Animal, ועוד קצת". ל-rex אין מתודת speak משלו, אבל החיפוש ממשיך ל-Animal, שיש לו. ההמשך הזה למעלה הוא כל המנגנון: זו פשוט שרשרת prototype עם תחביר נחמד יותר.

super בבנאי

לתת-מחלקה עם בנאי משלה יש כלל אחד נוקשה: לקרוא ל-super(...) לפני שנוגעים ב-this. super מריצה את הבנאי של מחלקת האב, והוא זה שבפועל יוצר ומאתחל את האובייקט:

דלגו על השורה super(name) ותקבלו ReferenceError ברגע שתנסו לקרוא או לכתוב ל-this. המנוע פשוט מסרב לתת לכם this עד שמחלקת האב קיבלה את התור שלה.

אם תת-מחלקה לא מצהירה על בנאי משלה, JavaScript יוצרת בנאי שמעביר את כל הארגומנטים ל-super, כך שצריך לכתוב בנאי רק כשמוסיפים שדות או מבצעים הגדרות נוספות.

דריסת מתודות

תת-מחלקה יכולה להגדיר מחדש כל מתודה שהיא יורשת. הקרובה ביותר בשרשרת מנצחת:

אין כאן קסם: כשקוראים ל-speak() על Dog, המנוע מחפש את speak על המופע, אחר כך על Dog.prototype, מוצא אותה ועוצר. הוא אף פעם לא מגיע ל-Animal.prototype.

להרחיב, לא להחליף: super.method()

לפעמים לא רוצים להחליף את המתודה של מחלקת האב, אלא להוסיף עליה. super.method(...) קוראת לגרסה של מחלקת האב מתוך הדריסה:

כאן ירושה מצדיקה את עצמה: תת-המחלקה משתמשת שוב בלוגיקה של מחלקת האב במקום להעתיק אותה. אם Animal.describe תשתנה בהמשך, Dog.describe תקבל את השינוי בחינם.

super עובדת בכל מתודה, לא רק בבנאי. היא תמיד מתייחסת לגרסה של מחלקת האב של מה שאתם קוראים לו.

instanceof והשרשרת

instanceof בודק אם שרשרת ה-prototype של אובייקט כוללת מחלקה מסוימת. כל מופע של תת-מחלקה הוא גם מופע של מחלקות האב שלה:

כל הארבעה הם true. השרשרת היא Puppy -> Dog -> Animal -> Object, ו-instanceof עובר עליה. שימושי לבדיקות טיפוס, אם כי בפועל תשתמשו בו פחות ממה שנדמה: רוב הקוד פשוט קורא למתודות ונותן לפולימורפיזם לטפל בשאר.

דוגמה קצת יותר גדולה

תבנית נפוצה: מחלקת בסיס עם לוגיקה משותפת, וכמה תת-מחלקות שמתמחות בה.

שימו לב ש-describe נמצאת ב-Shape ואף פעם לא צריך לכתוב אותה מחדש: היא פשוט קוראת ל-this.area(), שמתפרשת לתת-המחלקה הנכונה בזמן ריצה. זה פולימורפיזם: אותו מקום קריאה, התנהגות שונה לפי האובייקט בפועל.

ירושה מול composition

ירושה מפתה כי היא מרגישה פרודוקטיבית: שורה אחת נותנת ערימה של מתודות. היא מזדקנת רע כשההיררכיות גדלות.

כלל אצבע: השתמשו ב-extends כשהיחס הוא בבירור "X הוא סוג של Y" ותת-המחלקה באמת חולקת את רוב ההתנהגות של מחלקת האב. כשאתם פונים לירושה רק כדי לשתף מתודת עזר אחת או שתיים, העדיפו composition: תנו למחלקה שדה שמחזיק את אובייקט העזר:

עצי ירושה עמוקים (Animal -> Mammal -> Dog -> WorkingDog -> PoliceDog) נראים מסודר בתרשימים ונעשים כואבים בקוד: שינוי קרוב לשורש מתגלגל באופן בלתי צפוי לכל הצאצאים. רוב בסיסי הקוד הבריאים נשארים ברמה אחת או שתיים ונשענים על composition לכל השאר.

הבא בתור: Static Members

כל מה שבעמוד הזה נמצא על מופעים: מתודות שקוראים להן דרך new Thing().something(). לפעמים רוצים מתודות או נתונים ששייכים למחלקה עצמה, לא למופע מסוים. בשביל זה קיים static, והוא הנושא הבא.

שאלות נפוצות

איך ירושה עובדת ב-JavaScript?

מחלקה אחת יכולה לרשת ממחלקה אחרת בעזרת extends. תת-המחלקה מקבלת כל מתודה ושדה של מחלקת האב, ויכולה להוסיף משלה או לדרוס קיימים. מאחורי הקלעים, JavaScript מקשרת את ה-prototype של תת-המחלקה ל-prototype של מחלקת האב, כך שחיפוש מתודות ממשיך למעלה באופן אוטומטי.

מה super עושה ב-JavaScript?

super(...) קוראת לבנאי של מחלקת האב, וחובה לקרוא לה לפני שמשתמשים ב-this בבנאי של תת-מחלקה. super.method(...) קוראת לגרסה של מחלקת האב למתודה, וכך מרחיבים התנהגות במקום להחליף אותה לגמרי.

האם להשתמש בירושה או ב-composition ב-JavaScript?

השתמשו בירושה כשיש יחס אמיתי של 'הוא סוג של' ותת-המחלקה חולקת את רוב ההתנהגות של מחלקת האב. בחרו ב-composition, אובייקטים שמחזיקים אובייקטים אחרים, כשאתם רק רוצים לשתף פונקציונליות. היררכיות מחלקות עמוקות נוטות להזדקן רע, ורוב בסיסי הקוד האמיתיים נשארים ברמה אחת או שתיים.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל