המילה this ב-JavaScript: הסבר מלא

Logo of JWT, JSON Web Token.

מה זה this keyword ב-JavaScript?

המילה this ב-JavaScript היא מילה מיוחדת המייצגת את האובייקט שקורא לפונקציה. המשמעות של this משתנה בהתאם להקשר שבו הפונקציה נקראת – זהו אחד הנושאים המרכזיים ב-JavaScript שעלולים לבלבל מתכנתים, במיוחד למתחילים. כדי להבין לעומק את this, חשוב להבין מהו האובייקט הגלובלי.

console.log(this);

מהו האובייקט הגלובלי?

כדי להבין לעומק את this, חשוב להבין מהו האובייקט הגלובלי.

האובייקט הגלובלי הוא האובייקט הראשי שמייצג את סביבת העבודה של הקוד שלכם. כל קוד שכותבים ב-JavaScript פועל בתוך סביבת עבודה מסוימת (דפדפן, שרת Node.js וכו'), והאובייקט הגלובלי הוא ה״מרכז״ של אותה סביבת עבודה, שבו נשמרים כל המשתנים והפונקציות שהוגדרו ברמה העליונה של הקוד – כלומר, מחוץ לפונקציות או אובייקטים אחרים.

האובייקט הגלובלי בדפדפן

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

לדוגמה, window מכיל פונקציות מוכרות כמו ()alert להצגת הודעות קופצות, ()confirm להצגת חלון אישור עם כפתורי "אישור" ו-"ביטול", ו-()addEventListener שמאזינה לפעולות המשתמש כמו לחיצות עכבר או טעינת עמוד.

האובייקט window מחזיק גם משתנים גלובליים שמוגדרים ללא מילות מפתח כמו let, const או var. משתנים כאלה נשמרים ישירות בתוך window, ולכן חשוב להימנע מהגדרה לא זהירה של משתנים גלובליים כדי לא לגרום להתנגשויות בקוד.

בנוסף, window מכיל אובייקטים גלובליים חשובים לניהול הדף, כמו document, שמאפשר גישה לעץ ה-DOM, ו-location, שמייצג את כתובת ה-URL של העמוד.

console.log(window); // window object in browser
window.addEventListener('click', () => console.log(window));
console.log(window.document.querySelector('h2'));

האובייקט הגלובלי ב-Node.js

ב-Node.js, האובייקט הגלובלי נקרא global, והוא מקביל ל-window בדפדפן, אך מותאם להרצה בצד השרת. בשונה מהדפדפן, האובייקט global אינו כולל פונקציות ואירועים הקשורים לממשק המשתמש, אלא רק פונקציות שקשורות להרצת תוכניות ולתהליכי מערכת בצד השרת.

לדוגמה, global כולל פונקציות כמו ()setTimeout ו-()setInterval לניהול זמן, אך אינו כולל פונקציות כמו ()alert או אירועים כמו onClick, מכיוון שאלה שייכים לסביבה של הדפדפן בלבד. במקום זאת, global מכיל אובייקטים כמו process, שמספק מידע על התהליך שמריץ את הקוד, ופונקציות כמו ()require לייבוא מודולים חיצוניים.

ב-Node.js ניתן להדפיס לטרמינל הודעות באמצעות ()console.log, ממש כפי שעושים בקונסול של הדפדפן, אך לא ניתן להשתמש באובייקט document או בגישה ל-DOM, מכיוון שאין ממשק משתמש גרפי בצד השרת.

console.log(global); // global object in Node.js
console.log(global.process); // process object in Node.js (env vars, memory, etc.)
console.log(global.require); // require function in Node.js

📌 האובייקט הגלובלי ב-JavaScript שונה בהתאם לסביבה שבה הקוד רץ. בדפדפן, window משמש לניהול פעולות הקשורות לעמוד האינטרנט ולממשק המשתמש, בעוד שב-Node.js, האובייקט global מאפשר לנהל תהליכים בצד השרת ומספק כלים לעבודה עם מערכת הקבצים והפלט.

דוגמאות לשימוש ב-this

אוקיי, אחרי שהבנו מהו ה-Global Object, הגיע הזמן לחזור למילה this ולראות איך היא "מצביעה" בהתאם להקשר שבו היא מופעלת. אז הנה דוגמה לשימוש במתודה עם this.

const object = {
  name: "Saar",
  age: 23,
  printThis() {
    console.log(this); // { name: 'Saar', age: 23, printThis: [Function: printThis] }
  }
};

object.printThis();

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

במקרה הזה, הקריאה ()object.printThis גורמת לכך ש-this יפנה לאובייקט עצמו ויציג את כל המאפיינים שלו – name, age, והפונקציה printThis עצמה כחלק מהאובייקט.

הבעיה של this keyword

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

const example2 = {
  eventName: "Birthday Party",
  guestList: ['Novin', 'Saar', 'Ofri'],
  printGuestList() {
    console.log(`Guest list for ${this.eventName}`); // מציג "Birthday Party"

    this.guestList.forEach(function (guest) {
      console.log(`${guest} is attending ${this.eventName}`); // undefined
    });
  }
};

example2.printGuestList();

כאשר הפונקציה printGuestList נקראת מתוך האובייקט example2, המשתנה this מתייחס לאובייקט example2, ולכן this.eventName מציג את שם האירוע "Birthday Party". עם זאת, בתוך הקריאה ל-forEach, הפונקציה הפנימית נכתבת כ"פונקציה רגילה", מה שמוביל להגדרת הקשר חדש עבור this.

בעקבות זאת, this.eventName מחפש את המשתנה eventName באובייקט הגלובלי (window בדפדפן או global ב-Node.js). כיוון שבאובייקט הגלובלי לא קיים משתנה בשם eventName, התוצאה המתקבלת היא undefined.

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

הפתרונות לבעיית this keyword

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

הפתרון הראשון: שימוש במשתנה עזר (that)

אחת הדרכים להתמודד עם בעיית ההקשר של this היא לשמור את הערך של this במשתנה עזר (that) מחוץ לפונקציה הפנימית. כך, במקום שהפונקציה הפנימית תיצור הקשר חדש של this, היא תשתמש במשתנה that, שמחזיק את ההקשר המקורי.

const example2 = {
  eventName: "Birthday Party",
  guestList: ['Novin', 'Saar', 'Ofri'],
  printGuestList() {
    const that = this;
    console.log(`Guest list for ${that.eventName}`); // מציג "Birthday Party"

    this.guestList.forEach(function (guest) {
      console.log(`${guest} is attending ${that.eventName}`); // משתמש ב-'that' במקום ב-'this'
    });
  }
};

example2.printGuestList();

המשתנה that שומר את הערך של this, אשר מתייחס לאובייקט example2. מאחר שהפונקציה הפנימית forEach אינה מגדירה הקשר חדש עבור that, היא יכולה לגשת לשם האירוע באמצעות that.eventName. כתוצאה מכך, ההדפסה מציגה את השם "Birthday Party" במקום undefined.

הפלט שיוצג בקונסול בעת קריאה לפונקציה:

Guest list for Birthday Party
Novin is attending Birthday Party
Saar is attending Birthday Party
Ofri is attending Birthday Party

הפתרון השני: שימוש בפונקציות חץ (Arrow Functions)

פונקציות חץ הן פונקציות ב-JavaScript שנועדו לפתור בעיות כמו הקשר של this בפונקציות פנימיות. פונקציות חץ אינן מגדירות הקשר חדש של this, ולכן משתמשות בהקשר של הפונקציה החיצונית.

const example2 = {
  eventName: "Birthday Party",
  guestList: ['Novin', 'Saar', 'Ofri'],
  printGuestList() {
    console.log(`Guest list for ${this.eventName}`); // מציג "Birthday Party"

    this.guestList.forEach((guest) => {
      console.log(`${guest} is attending ${this.eventName}`);
    });
  }
};

example2.printGuestList();

הפונקציה הפנימית forEach משתמשת בפונקציית חץ, שאינה מגדירה הקשר חדש של this. פונקציית החץ משתמשת בהקשר של הפונקציה החיצונית, ולכן this.eventName מצביע לשם האירוע "Birthday Party". כתוצאה מכך, ההדפסה מציגה את השם "Birthday Party" במקום undefined.

הפלט שיוצג בקונסול בעת קריאה לפונקציה:

Guest list for Birthday Party
Novin is attending Birthday Party
Saar is attending Birthday Party
Ofri is attending Birthday Party

סיכום

מילת המפתח this ב-JavaScript היא אחד הנושאים המבלבלים בשפה, אך האמת היא שלא בכל סביבה חייבים להשתמש בה. לדוגמה, בעבר ב-React עשו בה שימוש נרחב, אבל מאז המעבר ל-hooks השימוש ב-this הפך לכמעט מיותר.

לעומת זאת, ב-Angular, שבו מבנה הקוד מבוסס על מחלקות, this עדיין נמצא בשימוש יומיומי כחלק מהעבודה עם מחלקות ושירותים. רוצים ללמוד עוד על מחלקות ובכלל על תכנות מונחה עצמים (OOP)? קראו את המאמר המלא שלנו.