עבור לתוכן
Alex Kovalev

להבין את ההקשר של THIS ב-JS

איך עובדת הגדרת משתנים ב-JS? למה פונקציות יורשות this?

3דקות קריאה

אני מבולבל לגבי ‘this’. אוקיי? אמרתי את זה.

  • איך הוא נקבע?
  • למה הוא שונה עבור פונקציית חץ?
  • ולמה פונקציות רגילות לא מקבלות אחד? אלה השאלות שאני רוצה לענות עליהן במאמר הזה.

THIS לא מתייחס להקשר (scope לקסיקלי)

אתם צריכים לדעת שכשאתם כותבים פונקציה כמו שאני עושה כאן:

var name = "alex"
function greet() {
console.log("hi "+this.name);
}
greet() // מה קורה כאן?

מה זה מדפיס?<script> רגיל בדפדפן, לא מודול. אותו דבר לשאר.

הצגת הפלטhi alex

קריאה חשופה בסקריפט רגיל, אז this הוא window. var גלובלי נדבק ל-window, אז this.name הוא "alex". במודול זה נזרק: שם this הוא undefined.

אותו קוד לא ירוץ בג’אווה, C או PHP. למעשה גם ב-JS הוא לא היה מדפיס alex אם לא הייתי מגדיר את name באמצעות var. זו הערת אגב קטנה, אבל משתנים שמוגדרים עם var חיים ב-global scope ולא בהקשר הלקסיקלי.

מוגדרvarlet ו-const
גלובליתglobal scopeהקשר לקסיקלי גלובלי
בפונקציהfunction scopeהקשר לקסיקלי בהיקף הפונקציה
בבלוקה-scope של הפונקציה העוטפת (או global scope)הקשר לקסיקלי בהיקף הבלוק

הנה הדגמה מהירה להבדל:

if (true) {
var v_name = "alex"
let l_name = "alex"
}
console.log(v_name) // "alex"
console.log(l_name) // ReferenceError: l_name is not defined

אבל למה this.name מחזיר var שהוגדרה גלובלית? זה מפני שבסביבה לא strict כמו הדפדפן, ה-‘this’ שמוגדר גלובלית מתייחס לאובייקט window, ו-var שמוגדרת ב-global scope נכנסת בדיוק לשם. אבל למעשה זה פשוט כי כל קוד הדפדפן, מבחינה טכנית, רץ בתוך אובייקט ה-window, ואם חושבים על זה בצורה הזאת, זה נהיה די ברור למה המשתנים נדבקים אליו ולמה פונקציות יורשות אותו.

הנה כמה מקורות לקריאה נוספת https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#temporal_dead_zone_tdz

THIS הוא סוכר תחבירי

אי פעם תהיתם איך אובייקטים קיבלו את מקומם ב-js? איך js מתייחס לאובייקטים? תסתכלו על זה: בואו נגדיר אובייקט User פשוט

class User {
name = "";
constructor(name) {
this.name = name;
}
greet() {
console.log("hi "+this.name)
}
}

כשאנחנו קוראים לפונקציה מהמופע של אותו אובייקט, מה שקורה בסוף הוא:

const alex = new User("alex")
alex.greet()
// שקול ל:
alex.greet.call(alex)
// או
alex.greet.apply(alex, [])

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

אז הקוד הבא לא יברך את alex

const name = "alex";
function greet() {
console.log("hi "+this.name);
}
greet()
// שקול ל
greet.call(undefined);

מה זה מדפיס?

הצגת הפלטhi ␣

this עדיין window, אבל const לא נדבק אליו. אז this.name קורא את window.name, מאפיין אמיתי של הדפדפן שהוא מחרוזת ריקה כברירת מחדל. לא undefined. דפדפנים.

וגם זה לא יעבוד

function setup(){
const name = "alex";
function greet() {
console.log("hi "+this.name);
}
}
greet()
// היה שקול ל (אם greet הייתה קיימת כאן)
greet.call(undefined);

מה זה מדפיס?

הצגת הפלטUncaught ReferenceError: greet is not defined

שאלה מכשילה. greet קיימת רק בתוך setup(), אז כאן בחוץ אין מה לקרוא. זה אף פעם לא מגיע לשלב שבו מתבלבלים עם this.

כדי להבין טוב יותר את מה שכתבתי עכשיו, עברו על הדוגמה הבאה תוך ניסיון לנחש מה הפלט של התוכנית

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

class User {
name = "";
constructor(name) {
this.name = name;
}
normalGreet(){
console.log("hi "+this.name)
}
arrowGreet = () => {
console.log("hi "+this.name)
}
}
let user = new User("alex");
let miri = new User("miri")

שאלה ראשונה

let alex = new User("alex")
// ראשון
alex.normalGreet();
alex.arrowGreet();

מקווה שלא אקלקל את זה שגם וגם עובדות מצוין ומדפיסות את הודעת הברכה שלנו עבור alex - hi alex

שאלה שנייה

let alex = new User("alex");
let normal = alex.normalGreet;
let arrow = alex.arrowGreet;
normal();
arrow();

מה זה מדפיס?

הצגת הפלטUncaught TypeError: Cannot read properties of undefined (reading 'name')

קוד בתוך class תמיד רץ ב-strict mode, אז normal() מנותקת מקבלת this === undefined במקום window, וקריאת .name ממנו זורקת שגיאה. arrow() לא מגיעה לתורה.

פה מתחילה הבעיה, אתם מצליחים לזהות אותה?

מה קורה כשאנחנו משייכים את הפונקציה למשתנה לפני שקוראים לה?

מסתבר שהפונקציה הרגילה מתפוצצת עם TypeError: Cannot read properties of undefined (reading 'name'), עכשיו תחשבו למה. אמרנו שלפונקציה רגילה יש this דינמי, שמחושב בזמן ריצה. תחשבו על זה, אנחנו קוראים לפונקציית greet כאילו על כלום, אין לה הקשר this, אז איך היא יכולה להתייחס ל-this.name? (גוף של מחלקה תמיד רץ ב-strict mode, אז this כאן הוא פשוט undefined במקום ליפול בשקט ל-window.) היא נזרקת עוד לפני ש-arrow() מגיעה לתורה.

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

אתם אולי שואלים את עצמכם, האם אני באמת צריך לדעת את זה? באיזה הקשר אני אראה את זה? התשובה תלויה בשימוש שלכם ב-OOP ב-JS או בכל פריימוורק אחר. לדוגמה

<button onclick={alex.normalGreet}>Normal Greet</button>
<button onclick={alex.arrowGreet}>Arrow Greet</button>

אפשר להסתכל על הקוד למעלה כשיוך פונקציה ל-property של אובייקט!

// זה מעתיק בדיוק את הדוגמה השנייה שלנו
buttonElement.onclick = alex.normalGreet

אז כדי שה-tsx שלנו יעבוד, תצטרכו או להשתמש בפונקציית חץ באובייקט User, או להעביר אותה ככה

<button onclick={()=>alex.normalGreet()}>Normal Greet</button>

ראו גם: Livewire ל-WP, שבו onclick מוטמע מעביר ל-handler את this (הכפתור עצמו).

החלק השלישי הוא סתם בשביל הכיף, למקרה שתרצו להבין טוב יותר איך bind, call ו-apply עובדים:

let miri = new User("miri")
let normal = alex.normalGreet;
let arrow = alex.arrowGreet;
normal = normal.bind(miri);
arrow = arrow.bind(miri);
normal();
arrow();

מה זה מדפיס?

הצגת הפלטhi mirihi alex

bind קובע את this לפונקציה רגילה, אז normal מברכת עכשיו את miri. פונקציית החץ כבר לקחה את ה-this שלה מהמקום שבו נכתבה, המופע alex, ו-bind לא יכול לקחת אותו בחזרה.

הפונקציה הרגילה מדפיסה hi miri. bind מחזיר פונקציה חדשה שה-this שלה ממוסמר למה שהעברתם לו, ופונקציה רגילה מקבלת את זה בשמחה.

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

אז, בחזרה לשאלות שלי מההתחלה. this נקבע בזמן הקריאה, לפי מה שנמצא משמאל לנקודה (או לפי call, apply ו-bind). פונקציות חץ שונות כי אף פעם אין להן this משלהן - הן שואלות את זה של המקום שבו הן נכתבו. וכשקוראים לפונקציה על כלום, אין מה לקבוע, אז מקבלים undefined (או window, אם אתם ב-sloppy mode וחסרי מזל).

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

5 קטעי קוד, 5 ניחושים. הציון יופיע כאן.

תגובות

התגובות נשמרות ב-GitHub Discussions (דרך giscus), אז כדי להצטרף לשיחה צריך חשבון GitHub.

אצייץ לך כשיעלה פוסט חדש

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

מעדיפים קורא RSS? יש פיד RSS.

© Alex Kovalev. כל הזכויות שמורות. · מדיניות פרטיות