המשתנה this בקוד טיפול באירוע
תבנית קוד המחלקות שהצגתי בשעור הקודם כללה מוקש ייחודי ל JavaScript, לכן הגיע הזמן להרחיב את הדיבור על המשתנה המיוחד this ועל דרכים טובות יותר ופחות להשתמש בו. אחרי שנבין את זה יהיה קל יותר ליישם ארכיטקטורה מונחית עצמים ולכתוב אתה תוכניות מועילות.
1. מה קורה ל this בעת טיפול באירוע
הקוד הבא מתאר מחלקה פשוטה הכוללת שדה מידע יחיד ובו מספר ופונקציה יחידה המגדילה את המספר ומדפיסה את הערך החדש:
function Counter() {
this.val = 0;
}
Counter.prototype.inc = function() {
this.val++;
console.log(this.val);
};
var c = new Counter();
// prints 1
c.inc();
// prints 2
c.inc();
נסיון לחבר את הקוד הזה עם מנגנון האירועים של הדפדפן עשוי להפתיע. כך נראה הקוד אחרי שהוספתי לו כפתור וחיברתי את הפונקציה לאירוע לחיצה על הכפתור. נסו ללחוץ ולראות מה קורה ולאחר מכן נסו להבין מה השתבש:
התוצאה היא הדפסת המילה NaN עם כל לחיצה על הכפתור, אבל למה? מקור הבעיה במשתנה המיוחד this.
בהפעלה רגילה של inc המשתנה this יקבל את האוביקט שנמצא מצד שמאל לסימן הנקודה, כלומר את אוביקט המונה עצמו:
var c = new Counter();
// calling inc() with c as the value of "this"
c.inc();
בקוד שלנו לעומת זאת לא הפעלנו את הפונקציה inc באותו האופן. נזכר בשורה הרלוונטית מתוך הקוד:
btn.addEventListener('click', c.inc);
היעדר הסוגריים לאחר שם הפונקציה מעיד על כך שבמקום להפעיל אותה בחרנו לקחת את הפונקציה עצמה ולשלוח אותה לפונקציה addEventListener. פונקציה זו בתורה תפעיל את פונקציית inc שלנו, אך בהפעלה עתידית זו המערכת תעביר את האלמנט שיצר את האירוע בתור המשתנה this, במקום את אוביקט המונה שלנו.
התוצאה שבמשתנה this שהועבר ל inc לא קיים שדה בשם val, ולכן הנסיון להגדילו נכשל ומחזיר את התוצאה NaN כלומר Not a Number: ב JavaScript כשמחברים undefined עם 1 מקבלים NaN.
2. הפתרון: כתיבת פונקציה עוטפת
מאחר והבעיה היא באופן הפעלת הפונקציה inc, הפתרון יהיה לקבל חזרה את השליטה בהפעלה זו. דרך קלה לעשות זאת היא לכתוב פונקציה חדשה העוטפת את הקריאה לפונקציה inc, ולהעביר את הפונקציה החדשה הזו כפרמטר ל addEventListener. כך זה נראה:
3. קיצור דרך: שמוש בפונקציה bind
הפונקציה bind של JavaScript מייצרת אוטומטית פונקציית עטיפה כמו זו שכתבנו בסעיף הקודם. הפרמטר הראשון ל bind הוא האוביקט שיועבר בתור this ולאחריו ניתן להעביר רשימת פרמטרים שתועבר לפונקציה הפנימית. bind מחזירה את הפונקציה החיצונית, בה כבר מותר להשתמש כקוד טיפול באירוע.
להלן אותו הקוד עם הפונקציה bind:
function Counter() {
this.val = 0;
}
Counter.prototype.inc = function() {
this.val++;
log.innerHTML += this.val + '<br />';
};
var c = new Counter();
var btn = document.querySelector('button');
btn.addEventListener('click', c.inc.bind(c));
שימו לב שהמשתנה c מופיע כעת פעמיים בשורה האחרונה: תחילה כחלק משם הפונקציה c.inc
אותה אנו רוצים ״לקשור״, ולאחר מכן כפרמטר בתור הערך של משתנה this בעת הפעלת הפונקציה.
4. סיכום: תבנית מחלקה עם חיבור מתודות לאירועים
כדי לחסוך קוד ולהקל על האנשים שמשתמשים במחלקה שלכם, מומלץ לחבר את הפונקציות לאירועים מתוך קוד הבנאי. כך גם מי שמשתמש בקוד לא צריך לזכור להפעיל bind. בדוגמא הבאה ניתן לראות מחלקה עבור מונה המקבלת את הכפתור כפרמטר לפונקציית הבנאי ומבצעת את החיבור כבר מקוד הבנאי.
מאחר והחיבור הוא פנימי, קל לבנות מספר כפתורים שיחוברו למספר מונים.
כדי לחסוך קוד ולהקל על האנשים שמשתמשים במחלקה שלכם, מומלץ לחבר את הפונקציות לאירועים מתוך קוד הבנאי. כך גם מי שמשתמש בקוד לא צריך לזכור להפעיל bind. בדוגמא הבאה ניתן לראות מחלקה עבור מונה המקבלת את הכפתור כפרמטר לפונקציית הבנאי ומבצעת את החיבור כבר מקוד הבנאי.
מאחר והחיבור הוא פנימי, קל לבנות מספר כפתורים שיחוברו למספר מונים.