MDN wants to learn about developers like you: https://www.surveygizmo.com/s3/5171903/MDN-Learn-Section-Survey-Recruiter-Pathway

Prototypes - אבי טיפוס, זה המנגנון שבאמצעותו אובייקטים של javascript יורשים מאפיינים אחד מהשני. במאמר זה אנחנו נסביר כיצד שרשראות אבי טיפוס - (prototype chains) עובדות ונסתכל כיצד מאפיין (property) בשם prototype יכול לשמש עבור הוספת מתודות ל-constructors קיימים.

ידע מוקדם:

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

מטרה: הבנה של JavaScript object prototypes, כיצד שרשראות prototype עובדות, וכיצד להוסיף מתודות חדשות לתוך פרופ׳ בשם prototype .

שפה מבוססת אב-טיפוס?

JavaScript לעיתים קרובות מתוארת כשפה מונחית אבי-טיפוס (prototype-based language) - על מנת לאפשר הורשה, לאובייקטים יכול להיות אובייקט אב-טיפוס (prototype object), אשר מתפקד כאובייקט תבנית - אובייקט אב טיפוס אשר ממנו האובייקטים יכולים לירוש מתודות (methods) ומאפיינים (properties).

לאובייקט אב-הטיפוס (prototype), יכול להיות גם אובייקט אב-טיפוס משל עצמו, אשר ממנו הוא יורש מתודות (methods) ומאפיינים (properties) וכך הלאה. תהליך זה לרוב מוגדר כשרשרת אבי הטיפוס (prototype chain), אשר מסבירה מדוע לאובייקטים שונים יש מאפיינים ומתודות שזמינים עבורם, כאשר אלו בכלל הוגדרו באובייקטים אחרים. 

ליתר דיוק, המתודות והמאפיינים מוגדרים במאפיין (property) בשם prototype ב-constructor functions ולא באובייקטים עצמם שנוצרו (object instance).

ב-JavaScript, נוצר קשר בין האובייקט שנוצר (object instance) לבין אב הטיפוס/prototype שלו באמצעות ״הליכה״ על שרשרת אבי הטיפוס. אנו נוכל לראות את אב הטיפוס של אובייקט ב-property של האובייקט שנוצר בשם __proto__ . שם זה נגזר מ-prototype שמוגדר בכלל ב-constructor.  

לתשומת לב:  חשוב להבין שיש אבחנה בין אב הטיפוס של האובייקט  (object' s prototype) אשר זמין באמצעות (Object.getPrototypeOf(obj או באמצעות מאפיין __proto__לבין מאפיין (property) בשם אב-טיפוס (prototype) אשר נמצא ב-constructor functions. 

הראשון הוא מאפיין שיופיע על כל אובייקט שייווצר ויעיד מיהו אב הטיפוס של אותו אובייקט ואילו השני, אשר נמצא ב-constructor הוא בעצם מאפיין של אותו constructor. 

נסתכל על דוגמא על מנת להבין זאת לעומק.

הבנה של אובייקטים מסוג אב-טיפוס

נחזור לדוגמא הקודמת שבה סיימנו לכתוב את ה-constructor שלנו ()Person:

העלו את הדוגמא בדפדפן שלכם. אם אין לכם את הדוגמא עצמה או שהיא אינה עובדת, אנא השתמשו בדוגמא שלנו אשר נמצאת בקישור זה או בקוד המקור.

בדוגמא זו, אנחנו מגדירים את ה -constructor function שלנו כך:

function Person(first, last, age, gender, interests) {
  
  // property and method definitions
  this.first = first;
  this.last = last;
//...
}

אנו יוצרים אובייקטים מה-constructor function שהגדרנו כך:

var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);

אם תקלידו .person1 בקונסולה, אתם אמורים לראות שהדפדפן מנסה להשלים באופן אוטומטי אפשרויות הזמינות עבור אובייקט זה: 

ברשימה זו, אתם יכולים לראות את הפרופ׳ ומתודות שהוגדרו ב-constructor Person()name, age, gender, interests, bio, ו- greeting. בנוסף, אנחנו גם נראה פרופ׳ ומתודות נוספות - watch, valueOf וכד׳ - אלו מוגדרים בתבנית אב הטיפוס (prototype object) של ()Person, שזה  Object.

מה קורה כשאנחנו רוצים לקרוא למתודה, להפעיל מתודה על person1, אשר מוגדרת ב-Object?:

person1.valueOf()

המתודה הזו - ()Object.valueOf נורשת על ידי person1 בגלל שתבנית אב הטיפוס של האובייקט (הפונקציה ()Person) שיצר אותו היא  ()Object. המתודה ()valueOf מחזירה את הערך של האובייקט שהיא נקראה עליו: 

  • הדפדפן תחילה בודק לראות האם לאובייקט person1 יש את המתודה ()valueOf זמינה עליו, כפי שמוגדר ב-constructor שלו, ()Person
  • אם לא - הוא בודק האם לאב הטיפוס של ()Person, יש את המתודה ()valueOf זמינה עליו. יש לו ולכן היא זמינה עבור person1

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

לתשומת לב: אין דרך רשמית להיכנס לאובייקט אב הטיפוס של אובייקט בצורה ישירה  הקשרים שבין הפריטים בשרשרת מוגדר בתוך פרופ׳ פנימי - שנקרא [[prototype]] במסמכים של השפה - ראו גם ECMAScript

מרבית הדפדפנים המודרניים מכילים פרופ׳ שזמין שנקרא בשם __proto__, אשר מכיל את האובייקט שהוא תבנית האב של האובייקט הרלוונטי. לדוגמא, נסו להזין person1.__proto__ ו- person1.__proto__.__proto__ לראות איך זה נראה. החל מ-ECMAScript 2015 אנחנו יכולים לגשת לאובייקט אב הטיפוס של אובייקט באמצעות Object.getPrototypeOf(obj).

The prototype property: היכן שמגדירים מה מורישים

אז, היכן הפרופ׳ והמתודות שמורישים מוגדרים? אם אנחנו מסתכלים על הדף Object, אנחנו נראה רשימה מצד של שמאל של פרופ׳ ומתודות - הרבה יותר מאלו שראינו שזמינות עבור person1. למה? מכיוון שחלקם הורשו ל-person1 וחלקם לא. 

כפי שהוסבר למעלה, אלו שנורשו הם אלו שמוגדרים במאפיין (property) בשם prototype - כלומר אלו הם שמתחילים עם .Object.prototype ולא אלו שרק מתחילים עם Object. הערך של המאפיין prototype הוא אובייקט, אשר בעצם הוא מאחסן את כל הפרופ׳ והמתודות שאנחנו רוצים להוריש לאובייקטים בהמשך ה-prototype chain.

כך לדוגמא, ()Object.prototype.watch ו- ()Object.prototype.valueOf זמינות עבור כל אובייקט שיירש מ-Object.prototype, כולל מופעים חדשים של אובייקטים מה-()constructor Person

לעומת זאת, ()Object.is ו-()Object.keys לדוגמא, לא מוגדרים בתוך () ולכן לא נוריש אותם לאובייקטים אשר יירשו מ-Object.prototype. הם מתודות ופרופ׳ אשר זמינים רק עבור ה-constructor Object עצמו. 

לתשומת לב:  זה נראה מוזר - כיצד יכולה להיות מתודה שמוגדרת על ה-constructor, שהיא בעצמה פונקציה? פונקציות הם גם סוג של אובייקט - ראו הדף בנושא ()Function להמשך פרטים.

  1. אתם יכולים לבדוק את הפרופ׳ בשם ה-prototype של האובייקטים הנוכחיים שלנו - חזרו לדוגמא הקודמת והקלידו את הקוד הבא בקונסולה:
    Person.prototype
  2. אתם לא תראו יותר מדי אפשרויות מכיוון שלא הגדרנו כלום בפרופ׳ בשם prototype של ה-constructor. כברירת מחדל, הפרופ׳ בשם prototype של ה-constructor תמיד מתחיל ריק.
  3. כעת, נסו להזין את הקוד הבא:
    Object.prototype

אתם תראו מספר גדול של מתודות שמוגדרות ב-property בשם prototype של Object, אשר זמינות עבור אובייקטים שירשו מ-Object כפי שהסברנו למעלה.

אתם תראו דוגמאות כאלו של שרשרת ההורשה - prototype chain inheritance בכל JavaScript - נסו לחפש אחר מתודות ופרופ׳ אשר מוגדרים בפרופ׳ של האובייקטים הגלובליים String, Date, Number, ו-Array. לכולם יש מס׳ מתודות ופרופ׳ שמוגדרות באותו prototype. זו הסיבה לדוגמא שכשאר אנחנו יוצרים מחרוזת בצורה הבאה:

var myString = 'This is my string.';

ל-myString ישר יש מספר מתודות שימושיות שזמינות עבורו כמו () split(), indexOf(), replace וכד׳. 

לתשומת לב: אנו ממליצים לקרוא את המדריך המעמיק שלנו בנושא - Using prototypes in JavaScript לאחר שהבנתם את החלק הזה. החלק הזה בכוונה נכתב בצורה פשוטה (יחסית) על מנת להסביר את הרעיון שעומד מאחורי הנושא.

חשוב: המאפיין (prototype (property הוא אחד מהחלקים המבלבלים ב-JavaScript. אתם עלולים לחשוב ש-this מצביע על האובייקט אב טיפוס של האובייקט הנוכחי, אבל הוא לא (זה אובייקט פנימי שניתן לגשת אליו באמצעות __proto__, זוכרים? ).

המאפיין prototype הוא בעצם property שמכיל אובייקט, אשר על אובייקט זה אנחנו מגדירים את המתודות והפרופ׳ שנרצו שיורשו.

שימוש במתודה ()create

מוקדם יותר ראינו כיצד מתודת ()Object.create יכולה לשמש על מנת ליצור אובייקט חדש:

  1. לדוגמא, נסו להזין בקונסולה של הדוגמא הקודמת את הקוד הבא: 
    var person2 = Object.create(person1);
  2. מה ש-()create עושה בפועל היא ליצור אובייקט חדש מ-prototype מוגדר. כאן person2 מיוצר כאובייקט חדש באמצעות שימוש ב-person1 כאובייקט ה-prototype שלו, כאובייקט אב הטיפוס שלו. אתם יכולים לראות זאת באמצעות הזנת הקוד הבא בקונסולה:
  3. person2.__proto__

זה יחזיר לנו person1.

המאפיין (property) בשם constructor

לכל constructor function יש מאפיין (property) בשם prototype אשר הערך שלו הוא אובייקט. אותו אובייקט מכיל מאפיין (property) בשם constructor

המאפיין constructor הזה, מצביע ל-constructor function המקורית. כפי שתראו בהמשך, כאשר properties שמוגדרים על Person.prototype property, או באופן כללי, על המאפיין prototype של constructor function, שהוא בעצמו הוא אובייקט, הם נהיים זמינים עבור כל האובייקטים שייווצרו באמצעות ה-constructor בשם ()Person. המאפיין constructor זמין גם עבור האובייקט person1 וגם עבור האובייקט person2

  1. לדוגמא, נסו להזין את השורות הבאות בקונסולה:
    person1.constructor
    person2.constructor

    שתיהן אמורות להחזיר לנו ה-Person() constructor, שכן היא מכילה את ה-״הגדרה״ המקורית של אובייקטים אלו. טריק חכם הוא שניתן לשים סוגריים רגילות () בסוף המאפיין constructor (ובתוך הסוגריים להכניס פרמרטים הנדרשים ל-constructor, ככל ונדרשים), וואז נוצר לנו אובייקט חדש מאותו constructor. ה-constructor הוא בעצם פונקציה אחרי הכל, אז אפשר לקרוא לפונקציה באמצעות שימוש ב-() כמו שאנחנו יודעים. רק חשוב לשים את המילה השמורה new לפני, על מנת להגדיר שאנחנו רוצים שיווצר אובייקט חדש ולהשתמש בפונקציה הזו כ-constructor של אותו אובייקט. 

  2. נסו להזין את הקוד הבא בקונסולה:
    var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);
  3. כעת, נסו לגשת למתודות ולפרופ׳ של האובייקט החדש:
    person3.name.first
    person3.age
    person3.bio()

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

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

instanceName.constructor.name

נסו לדוגמא להזין את הקוד הבא:

person1.constructor.name

לתשומת לב:  הערך של constructor.name יכול להשתנות (כתוצאה מ-prototypical inheritance, binding, preprocessors, transpilers, etc. ועד), אז לדוגמאות מורכבות יותר, אנחנו נרצה להתשמש באופרטור instanceof במקום. 

שינוי של הפרופ׳ בשם prototype - הוספה/הסרה של מתודות

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

  1. חזרו לדוגמא המופיע בקישור oojs-class-further-exercises.htm וצרו עותק מקומי של קוד המקור source code. מתחת לקוד הנוכחי של של JavaScript, הוסיפו את הקוד הבא, אשר יוצר מתודה חדשה במאפיין prototype של constructor function:
  2. Person.prototype.farewell = function() {
      alert(this.name.first + ' has left the building. Bye for now!');
    };
  3. שמרו את הקוד והעלו את הדף בדפדפן, ונסו להזין את הקוד הבא :
    person1.farewell();

אתם אמורים לקבל הודעה קופצת מסוג alert, המכילה את שם ה-person שהוגדר ב-constructor. זה מאוד שימוש, אבל מה שיותר שימושי זה שכל שרשרת ההורשה עודכנה באופן דינאמי ואוטומטי והפכה את המתודה הזו לזמינה עבור כל האובייקטים שנוצרו באמצעות אותה constructor function. 

חשבו על זה לרגע, בקוד שלנו אנחנו הגדרנו את ה-constructor function, ואז יצרנו אובייקט מאותה constructor function, ואז הוספנו מתודה נוספות ל-prototype של אותה constructor function: 

function Person(first, last, age, gender, interests) {

  // property and method definitions

}

var person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']);

Person.prototype.farewell = function() {
  alert(this.name.first + ' has left the building. Bye for now!');
};

ומה שקרה זה שהמתודה שהגדרנו ()farewell נהייתה זמינה עבור person1 - והוא יכול לעשות בה שימוש, למרות שהוא הוגדר לפני שהגדרנו את המתודה החדשה. מגניב, לא?

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

יחד עם זאת, לעיתים נדירות נראה properties שמוגדרים ב-property הprototype מכיוון שהם לא ממש גמישים כשהם מוגדרים בצורה הזו. לדוגמא, ניתן להוסיף property בצורה הזו: 

Person.prototype.fullName = 'Bob Smith';

זה לא ממש מאפשר גמישות (שכן יכול להיות שקוראים לאובייקט person החדש בשם אחר), ולכן יהיה עדיף לבנות את המאפיין החדש בצורה הבאה: 

Person.prototype.fullName = this.name.first + ' ' + this.name.last;

יחד עם זאת, זה לא יעבוד שכן this יפנה לסקופ הגלובלי במקרה הנוכחי ולא לסקופ של הפונקציה. קריאה למאפיין זה תחזיר ערך של undefined undefined.

זה עובד מצוין על מתודה שהגדרנו למעלה ב-prototype מכיוון שהיא יושבת בתוך הסקופ של ה-function, והמתודה מועברת בתורה לסקופ של האובייקט שנוצר באמצעות constructor function. אז אולי נגדיר constant properties ב- prototype, כאלו שלא נשנה לעולם, אבל בכללי, זה עובד טוב יותר להגדיר properties בתוך ה-constructor. 

בעקרון, השימוש המקובל להגדרת אובייקטים הוא להגדיר את המאפיינים (properties) בתוך ה-constructor ואת המתודות בתוך ה-prototype. 

זה הופך הקוד לקל יותר לקריאה, שכן ה-constractor מכיל רק  את ההגדרות של properties ואילו המתודות מחולקות לבלוקים נפרדים. לדוגמא:

// Constructor with property definitions

function Test(a, b, c, d) {
  // property definitions
}

// First method definition

Test.prototype.x = function() { ... };

// Second method definition

Test.prototype.y = function() { ... };

// etc.

ניתן לראות שימוש בפועל בדוגמא school plan app של Piotr Zalewa.

לסיכום

מאמר זה כיסה את הנושא של JavaScript object prototypes, כולל כיצד prototype object chains מאפשרת לאובייקטים לירוש מתודות ופרופ׳ אחד מהשני, ראינו את ה-property בשם prototype וכיצד הוא יכול לשמש על מנת להוסיף מתודות ל-constructors וכן נושאים נוספים.

במאמר הבא אנחנו נראה כיצד ניתן להחיל הורשה של פונצקיונליות בין שני אובייקטים שניצור. 

במודול זה

Document Tags and Contributors

Contributors to this page: ItzikDabush
Last updated by: ItzikDabush,