מחלקה היא תוכנית אב לאובייקטים
מחלקה מתארת את הצורה וההתנהגות של סוג מסוים של אובייקט. מגדירים את תוכנית האב פעם אחת, ואז מייצרים ממנה כמה מופעים שצריך. לכל מופע יש נתונים משלו, אבל כולם חולקים את אותן מתודות.
המבנה הבסיסי:
class User { ... } מגדיר את תוכנית האב. new User(...) יוצר מופע. כל מופע מקבל name ו-email משלו, אבל שניהם חולקים את מתודת greet היחידה שמוגדרת במחלקה.
שני מופעים, שתי קבוצות נתונים, קבוצה אחת של מתודות. זה כל הרעיון.
ה-constructor מכין כל מופע
ה-constructor הוא מתודה מיוחדת שרצה בדיוק פעם אחת לכל מופע, ברגע ש-new יוצר אותו. התפקיד שלו הוא לאתחל את האובייקט הטרי, בדרך כלל על ידי העתקת הארגומנטים ל-this:
this בתוך הבנאי מתייחס למופע החדש שנבנה עכשיו. ההצבה this.x = x שמה את x על האובייקט הספציפי הזה. כל קריאה ל-new Point(...) מקבלת this משלה, ולכן x ו-y משלה.
אם לא כותבים בנאי, JavaScript נותנת לכם בנאי ריק בחינם. צריך לכתוב אחד רק כשבאמת יש הכנות לעשות.
new הוא מה שמפעיל את המנגנון
קריאה למחלקה בלי new זורקת שגיאה:
const p = Point(3, 4);
// TypeError: Class constructor Point cannot be invoked without 'new'
זה מכוון. new עושה ארבעה דברים לפי הסדר:
- יוצר אובייקט ריק חדש.
- מקשר אותו ל-prototype של המחלקה (שם גרות המתודות).
- מריץ את ה-
constructorכש-thisקשור לאובייקט החדש. - מחזיר את האובייקט החדש.
בלי new שום דבר מזה לא קורה. המחלקה אוכפת את הכלל כדי שלא תשכחו אותו בטעות: שיפור אמיתי לעומת הימים שלפני class, כששכחה של new הייתה משחיתה בשקט את האובייקט הגלובלי.
מתודות משותפות, שדות לכל מופע
מתודות שמוגדרות בגוף המחלקה גרות על ה-prototype: עותק אחד שכל המופעים חולקים. שדות מופע (כל מה שמציבים ל-this) שייכים לכל מופע בנפרד, עותקים נפרדים בכל פעם.
לכל אחד מהמופעים יש count משלו, כך שהגדלה של אחד לא משפיעה על השני. אבל a.increment ו-b.increment הם ממש אותה פונקציה: שמורה פעם אחת על ה-prototype, ונמצאת בכל פעם שקוראים לה על מופע. זו הסיבה שמחלקות זולות גם בכמויות גדולות: אלף מופעים לא יוצרים אלף עותקים של כל מתודה.
שדות מחלקה
אפשר להצהיר על שדות מופע בראש גוף המחלקה, מחוץ לבנאי:
הצהרות שדות רצות לפני גוף הבנאי, כאילו נכתבו בראש ה-constructor. הן שימושיות כשהערך ההתחלתי לא תלוי בארגומנטים של הבנאי: נקי יותר מלהעמיס על הבנאי this.count = 0; this.step = 1;.
כשהערכים כן תלויים בארגומנטים, השאירו את ההצבה בבנאי, שם הארגומנטים זמינים.
Getters ו-Setters
getter או setter נראים כמו מתודה אבל מתנהגים כמו גישה למאפיין. קוראים או מציבים בלי סוגריים, והמתודה רצה מאחורי הקלעים:
שימו לב לאופן הקריאה: t.fahrenheit (בלי סוגריים) קורא ל-getter, ו-t.fahrenheit = 100 מפעיל את ה-setter. מבחוץ, fahrenheit נראה כמו מאפיין רגיל, אבל הוא מחושב בזמן אמת מתוך celsius.
getters מצוינים לערכים נגזרים. setters מצוינים לאימות או לנרמול של ערך בזמן ההצבה. אל תגזימו איתם: אם getter יקר לחישוב, מי שקורא את הקוד יופתע ש"גישה למאפיין" עושה עבודה אמיתית.
תחביר מתודה מקוצר, שמות מחושבים ו-this
הגדרות מתודה במחלקה משתמשות בתחביר מקוצר: בלי מילת המפתח function, ובלי : בין השם לגוף:
החזרת this מ-add מאפשרת שרשור מתודות: כל קריאה מחזירה את אותו מופע, כך שהמתודה הבאה יכולה לרוץ עליו.
מלכודת אחת: מתודות לא קשורות אוטומטית למופע שלהן. אם שולפים מתודה וקוראים לה לבד, this הולך לאיבוד:
הקריאה g.hello() עובדת כי ה-. מספק את this. הקריאה fn() לבדה לא עובדת. אם צריך מתודה מנותקת שכבר קשורה מראש (מקרה נפוץ ב-event handlers), קשרו אותה בבנאי או השתמשו בשדה מחלקה שהוא arrow function: hello = () => \Hi, ${this.name}`;`.
דוגמה קטנה שעובדת
מחברים את כל החלקים: שדות, בנאי, מתודות ו-getter:
קריא במבט אחד: המחלקה מתארת בדיוק מה זה BankAccount ומה הוא יכול לעשות.
מחלקות הן בעצם פונקציות
דבר אחד שכדאי להפנים: class הוא בעיקר תחביר נוח. מתחת לפני השטח, מחלקה היא פונקציה, ליתר דיוק פונקציית בנאי, והמתודות שלה גרות על ClassName.prototype:
typeof User הוא "function". greet גרה על User.prototype. מופעים מוצאים את greet דרך שרשרת ה-prototype, אותו מנגנון שקיים בשפה מהיום הראשון. מחלקות רק נותנות תחביר נקי להגדרה שלו.
המודל המחשבתי הזה משתלם בהמשך: ירושה, instanceof ודיבוג של שרשראות prototype הופכים לברורים יותר ברגע שזוכרים שמחלקה היא פונקציה שהמתודות שלה תלויות על ה-prototype שלה.
הבא בתור: ירושה
עד עכשיו כל מחלקה עמדה לבדה. רוב העיצובים האמיתיים בונים היררכיות של מחלקות: Dog שהוא סוג של Animal, AdminUser שהוא סוג של User. מילות המפתח extends ו-super מטפלות בזה, והן הבאות בתור.
שאלות נפוצות
איך יוצרים מחלקה ב-JavaScript?
משתמשים במילת המפתח class ואחריה שם, ואז בלוק עם מתודת constructor ועם כל מתודה אחרת. יוצרים מופע עם new ClassName(...). לדוגמה: class User { constructor(name) { this.name = name; } } ואז new User('Ada').
מה עושה ה-constructor במחלקה ב-JavaScript?
ה-constructor רץ פעם אחת כשקוראים ל-new ClassName(...). התפקיד שלו הוא להכין את המופע החדש, בדרך כלל על ידי הצבת הארגומנטים ל-this כמאפייני מופע. אם לא כותבים אחד, JavaScript נותנת לכם בנאי ברירת מחדל ריק באופן אוטומטי.
מה ההבדל בין מחלקה לפונקציה ב-JavaScript?
מתחת לפני השטח, מחלקה היא פונקציה: ליתר דיוק, פונקציית בנאי שהמתודות שלה מחוברות ל-prototype. מילת המפתח class היא בעיקר תחביר נוח, אבל היא מחייבת קריאה עם new, תומכת ב-extends בצורה נקייה, והופכת את המתודות ללא ניתנות למנייה. בקוד חדש, העדיפו class על פני פונקציות בנאי שנכתבות ידנית.