לדלג לתוכן

מילת המפתח this בג'אווהסקריפט מוסברת

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

הכלל המרכזי - הכל תלוי באיך שקוראים לפונקציה

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

const user = {
  name: "נועה",
  greet() {
    console.log(this.name);
  },
};

user.greet(); // "נועה" - כי קראנו דרך user.greet()

this בתוך greet הוא user, כי כך קראנו לפונקציה - user.greet(). אבל אם לוקחים את אותה פונקציה בדיוק ומריצים אותה בלי האובייקט, זה נשבר:

const greetFunction = user.greet;
greetFunction(); // undefined - this כבר לא user!

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

ארבע דרכים שבהן this נקבע

  • קריאה כמתודה של אובייקט - obj.method() - this הוא obj.
  • קריאה רגילה של פונקציה - func() - this הוא undefined (במצב strict) או window/global.
  • קריאה עם new - new Constructor() - this הוא האובייקט החדש שנוצר.
  • קריאה עם call, apply או bind - קובעים את this באופן מפורש בעצמכם.
function sayHi() {
  console.log(this.name);
}

const person = { name: "אלון" };
sayHi.call(person); // "אלון" - קבענו את this במפורש

פונקציות חץ - כלל שונה לגמרי

פונקציות חץ (arrow functions) לא מקבלות this משלהן בכלל. הן פשוט "שואלות" את ה-this של הסביבה שבה הן הוגדרו, ולא משנה איך קוראים להן.

const user = {
  name: "נועה",
  greetLater() {
    setTimeout(() => {
      console.log(this.name);
    }, 1000);
  },
};

user.greetLater(); // "נועה" - החץ שואל this מהסביבה שמסביבו

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

איפה זה פוגש אתכם בריאקט

בקומפוננטות פונקציונליות בריאקט כמעט אף פעם לא נתקלים בבעיית this, כי לא משתמשים במחלקות, ומשתמשים בעיקר בפונקציות חץ. אבל אם אתם נתקלים בקוד ישן יותר שכתוב עם קומפוננטות מחלקה (class components), this מופיע כל הזמן, ולעיתים קרובות רואים קוד כמו:

class OldComponent extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log(this.props);
  }
}

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

call, apply ו-bind בקצרה

שלושתם נועדו לקבוע במפורש מה יהיה this. call ו-apply מריצים את הפונקציה מיד, ורק נבדלים באיך מעבירים ארגומנטים. bind לא מריץ מיד - הוא מחזיר פונקציה חדשה עם this "צרוב" בתוכה, לשימוש מאוחר יותר.

function introduce(greeting) {
  console.log(`${greeting}, ${this.name}`);
}

const bound = introduce.bind({ name: "שירה" });
bound("היי"); // "היי, שירה"

איך זוכרים את זה בפעם הבאה שמתבלבלים

תשאלו את עצמכם - "מי עומד לפני הנקודה כשהפונקציה נקראת?". אם אין נקודה בכלל, this כנראה לא מה שציפיתם. אם זו פונקציית חץ, תעלו שלב אחד למעלה ותשאלו את אותה שאלה על הסביבה שמסביבה.

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

לסיכום

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

יש לכם קוד שבו this מתנהג "מוזר"? תביאו אותו לדיסקורד ונפרק ביחד למה.

הצטרפו לקהילה בדיסקורד