this נקבע בזמן הקריאה
רוב הבלבול סביב this נובע מהנחה שגויה אחת: ש-this תלוי במקום שבו פונקציה מוגדרת. הוא לא. this תלוי באופן שבו הפונקציה נקראת.
הנה אותה פונקציה שנקראת בשתי דרכים:
אותה פונקציה, this שונה. בקריאה הראשונה יש user משמאל לנקודה, ולכן this הוא user. בקריאה השנייה אין כלום משמאל, ולכן this הוא undefined. הפונקציה עצמה לא השתנתה, מקום הקריאה השתנה.
זכרו את הרעיון הזה: כדי להבין מה זה this, הסתכלו על הקריאה, לא על ההגדרה.
ארבעת חוקי ה-Binding
יש ארבע דרכים שבהן this נקבע. כמעט כל שאלה על this שתהיה לכם אי פעם נפתרת כשמבינים איזה מהם חל.
1. קריאה למתודה: obj.fn()
כשפונקציה נקראת כמאפיין של אובייקט, this הוא אותו אובייקט:
מה שנמצא משמאל לנקודה הופך ל-this. פשוט כמו שזה נשמע.
2. קריאה רגילה: fn()
כשקוראים לפונקציה בלי אובייקט לפניה, this הוא undefined ב-strict mode (שמודולים ומחלקות מפעילים אוטומטית) או האובייקט הגלובלי במצב הרגיל:
מכאן מגיעות שגיאות ה-"this is undefined". שליפה של מתודה מהאובייקט שלה הופכת קריאה למתודה לקריאה רגילה:
אין counter. לפני הקריאה, אין binding. הפונקציה לא זוכרת מאיזה אובייקט היא הגיעה.
3. Binding מפורש: .call(), .apply(), .bind()
אפשר לכפות על this כל ערך שרוצים:
.call ו-.apply קוראים לפונקציה מיד; ההבדל ביניהם הוא רק באופן שבו מועברים הארגומנטים. .bind מחזיר פונקציה חדשה שבה this נעול לתמיד, מה ששימושי עבור callbacks.
4. קריאה עם new: new Fn()
קריאה לפונקציה עם new יוצרת אובייקט חדש וקושרת אותו ל-this:
מחלקות משתמשות במנגנון הזה מאחורי הקלעים. נפגוש אותן בפרק מאוחר יותר.
ל-Arrow Functions אין this משלהן
Arrow functions שוברות את החוקים שלמעלה, בכוונה. הן לא קושרות this בכלל: הן יורשות אותו מה-scope שמסביבן, קפוא ברגע שה-arrow מוגדרת:
ה-arrow ברמה העליונה של מודול לוכדת את ה-this של המודול, שהוא undefined. למרות שהיא נקראת בצורה user.arrow(), ה-arrow מסרבת לקשור מחדש.
זה נשמע כמו באג, אבל זו כל המטרה. Arrow functions מצטיינות בתוך מתודות, שבהן רוצים לשמור על ה-this החיצוני:
ה-arrow בתוך setInterval יורשת את this מ-start, שנקראה בצורה timer.start(). לכן this.seconds עובד. function רגילה במקום הזה הייתה מקבלת this משלה (מה ש-setInterval מעביר) ונשברת.
כלל אצבע: השתמשו ב-arrow functions עבור callbacks בתוך מתודות. השתמשו בפונקציות רגילות עבור המתודות עצמן.
מלכודת ה-Callback הקלאסית
זו הדרך הנפוצה ביותר שבה this משתבש. מעבירים מתודה כ-callback, והיא מאבדת את ה-binding שלה:
setTimeout קורא לפונקציה כקריאה רגילה, לא בצורה c.increment(). שלוש דרכים לתקן את זה:
שלושתן עובדות. עטיפת ה-arrow היא בדרך כלל הברורה ביותר.
this ברמה העליונה
ה-this ברמה העליונה תלוי במקום שבו הקוד שלכם רץ:
- סקריפט בדפדפן (לא מודול):
thisהואwindow. - מודול ES (כולל רוב הקוד המודרני שעובר bundling):
thisהואundefined. - מודול CommonJS של Node.js:
thisהואmodule.exports.
להפניה אמינה לאובייקט הגלובלי בכל הסביבות, השתמשו ב-globalThis:
בפועל לא כדאי להסתמך על this ברמה העליונה. השתמשו ב-globalThis כשאתם באמת צריכים את האובייקט הגלובלי, ובשאר המקרים העבירו ערכים באופן מפורש.
עץ החלטות מהיר
כשאתם רואים this ולא בטוחים מה הוא יהיה, עברו על הרשימה הזו:
- האם הפונקציה היא arrow function? אז
thisהוא מה שהיה ב-scope העוטף. התעלמו לגמרי ממקום הקריאה. - האם היא נקראה עם
new? אזthisהוא האובייקט החדש. - האם היא נקראה עם
.call,.applyאו כפונקציה קשורה (bound)? אזthisהוא מה שהועבר. - האם היא נקראה בצורה
obj.method()? אזthisהואobj. - האם היא נקראה כ-
fn()רגילה? אזthisהואundefinedב-strict mode.
הסולם הזה, בסדר הזה, מכריע בכל מקרה.
הבא בתור: Higher-Order Functions
עכשיו ש-this כבר לא תעלומה, אתם מוכנים לדפוס שהופך את JavaScript לכל כך אקספרסיבית: העברת פונקציות כערכים. בהמשך נסתכל על higher-order functions, פונקציות שמקבלות או מחזירות פונקציות אחרות, ועל האופן שבו הן מניעות מתודות של מערכים, event handlers ואת רוב קוד ה-JavaScript בעולם האמיתי.
שאלות נפוצות
למה this מתייחס ב-JavaScript?
this מתייחס ב-JavaScript?this מתייחס לאובייקט שעליו הפונקציה נקראת, לא למקום שבו היא הוגדרה. ב-user.greet(), this הוא user. בקריאה רגילה greet(), this הוא undefined ב-strict mode (או האובייקט הגלובלי במצב הרגיל). הכלל תלוי במקום הקריאה, לא במקום ההגדרה.
למה this הוא undefined בתוך הפונקציה שלי?
this הוא undefined בתוך הפונקציה שלי?כנראה שלפתם מתודה מהאובייקט שלה וקראתם לה לבד, או העברתם אותה כ-callback. const fn = user.greet; fn(); מאבד את ה-binding: אין אובייקט משמאל לנקודה במקום הקריאה. תקנו את זה עם .bind(user), עם עטיפה של arrow function, או על ידי קריאה בצורה user.greet().
במה this שונה ב-arrow functions?
this שונה ב-arrow functions?ל-arrow functions אין this משלהן. הן יורשות אותו מה-scope שמסביבן ברגע שהן מוגדרות. זה הופך אותן לאידיאליות עבור callbacks בתוך מתודות, שבהם רוצים לשמור על ה-this החיצוני. זה גם אומר ש-.call(), .apply() ו-.bind() לא יכולים לשנות את this של arrow function.
מה זה this ברמה העליונה של סקריפט?
this ברמה העליונה של סקריפט?בסקריפט רגיל בדפדפן, this ברמה העליונה הוא האובייקט window. במודול ES הוא undefined. במודולי CommonJS של Node.js הוא module.exports. ההפניה שעובדת בכל הסביבות היא globalThis, שתמיד מצביע על האובייקט הגלובלי.