4.2 מילת המפתח this הרצאה
מילת המפתח this¶
ב-JS, this היא מילת מפתח מיוחדה שמצביעה על ה"הקשר" שבו פונקציה רצה. בניגוד לפייתון, שבה self הוא פשוט הפרמטר הראשון של מתודה, ב-JS הערך של this נקבע לפי הדרך שבה הפונקציה נקראת, לא לפי המקום שבו היא מוגדרת.
זה אחד הנושאים הכי מבלבלים ב-JS, אבל הכללים עצמם פשוטים.
this במתודה של אובייקט¶
כשפונקציה נקראת כמתודה של אובייקט (עם נקודה), this מצביע על האובייקט:
const user = {
name: "Alice",
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
user.greet(); // "Hello, I'm Alice"
// this === user
זה מתנהג כמו self בפייתון:
# class User:
# def __init__(self, name):
# self.name = name
# def greet(self):
# print(f"Hello, I'm {self.name}")
ההבדל: בפייתון self מועבר אוטומטית כפרמטר ותמיד מצביע על האובייקט. ב-JS, this נקבע לפי מי שקרא לפונקציה.
this בפונקציה רגילה¶
כשפונקציה נקראת לבד (לא כמתודה), this הוא:
- ב-strict mode: undefined
- בלי strict mode: האובייקט הגלובלי (window בדפדפן, global ב-Node.js)
// without strict mode
function showThis() {
console.log(this === window); // true in browser
}
showThis();
בפייתון אין מקבילה לזה - פונקציה רגילה לא מכירה את self בכלל, רק מתודות של מחלקות.
this בפונקציית חץ - arrow function¶
לפונקציות חץ אין this משלהן. הן יורשות את this מהסקופ החיצוני (lexical this):
const user = {
name: "Alice",
greetRegular() {
// regular function - this is the object
console.log(`Regular: ${this.name}`);
},
greetArrow: () => {
// arrow function - this is inherited from outer scope (global/module)
console.log(`Arrow: ${this.name}`);
}
};
user.greetRegular(); // "Regular: Alice"
user.greetArrow(); // "Arrow: undefined"
למה arrow function שימושית? כשרוצים לשמר this בתוך callback:
const user = {
name: "Alice",
friends: ["Bob", "Charlie"],
showFriends() {
// 'this' here is user
// BUG: regular function callback loses 'this'
this.friends.forEach(function(friend) {
console.log(`${this.name} knows ${friend}`);
// this.name is undefined! 'this' is not user anymore
});
// FIX: arrow function inherits 'this' from showFriends
this.friends.forEach((friend) => {
console.log(`${this.name} knows ${friend}`);
// this.name is "Alice" - arrow keeps the outer 'this'
});
}
};
user.showFriends();
הכלל: השתמשו ב-arrow functions כשאתם רוצים ש-this יישאר כמו ב-scope החיצוני (למשל ב-callbacks). השתמשו בפונקציות רגילות למתודות של אובייקטים.
this ב-event handler¶
כשפונקציה משמשת כ-event handler, this מצביע על ה-element שקיבל את האירוע:
const button = document.querySelector("#myButton");
button.addEventListener("click", function() {
console.log(this); // the button element
this.style.backgroundColor = "red";
});
// with arrow function - this is NOT the button
button.addEventListener("click", () => {
console.log(this); // window or undefined (inherited from outer scope)
// this.style.backgroundColor = "red"; // ERROR!
});
אם רוצים את ה-element ב-arrow function, אפשר להשתמש בפרמטר event:
button.addEventListener("click", (event) => {
console.log(event.target); // the clicked element
console.log(event.currentTarget); // the element the listener is on
event.currentTarget.style.backgroundColor = "red";
});
this במחלקה - class¶
בתוך class, this מצביע על המופע (instance) - בדיוק כמו self בפייתון:
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, I'm ${this.name}`);
}
}
const alice = new User("Alice");
alice.greet(); // "Hello, I'm Alice"
// this === alice
אבל יש מלכודת - אם מוציאים מתודה מהאובייקט, this הולך לאיבוד:
const alice = new User("Alice");
const greet = alice.greet; // extracting the method
greet(); // "Hello, I'm undefined"
// 'this' is no longer alice!
נראה איך לפתור את זה בהמשך עם bind.
איבוד הקשר - losing context¶
זו הבעיה הנפוצה ביותר עם this - כשמעבירים מתודה כ-callback, ה-this הולך לאיבוד:
class Timer {
constructor() {
this.seconds = 0;
}
start() {
// BUG: 'this' is lost inside setTimeout callback
setInterval(function() {
this.seconds++; // TypeError: Cannot read property 'seconds' of undefined
console.log(this.seconds);
}, 1000);
}
}
const timer = new Timer();
timer.start(); // ERROR!
שלושה פתרונות:
פתרון 1 - arrow function (הכי נפוץ)¶
class Timer {
constructor() {
this.seconds = 0;
}
start() {
setInterval(() => {
this.seconds++; // arrow inherits 'this' from start()
console.log(this.seconds);
}, 1000);
}
}
פתרון 2 - bind¶
class Timer {
constructor() {
this.seconds = 0;
}
tick() {
this.seconds++;
console.log(this.seconds);
}
start() {
setInterval(this.tick.bind(this), 1000);
}
}
פתרון 3 - שמירה במשתנה (ישן, פחות מומלץ)¶
class Timer {
constructor() {
this.seconds = 0;
}
start() {
const self = this; // save reference
setInterval(function() {
self.seconds++;
console.log(self.seconds);
}, 1000);
}
}
bind, call, apply¶
שלוש מתודות שמאפשרות לקבוע את this באופן ידני.
bind - יוצרת פונקציה חדשה עם this קבוע¶
const user = {
name: "Alice",
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
const greet = user.greet;
greet(); // "Hello, I'm undefined"
const boundGreet = user.greet.bind(user);
boundGreet(); // "Hello, I'm Alice"
// bind can also pre-fill arguments (partial application)
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2);
console.log(double(5)); // 10
console.log(double(10)); // 20
bind שימושי במיוחד עם event handlers ו-callbacks:
class App {
constructor() {
this.count = 0;
// bind the method in the constructor
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.count++;
console.log(`Clicked ${this.count} times`);
}
init() {
document.querySelector("#btn").addEventListener("click", this.handleClick);
}
}
call - קוראת לפונקציה עם this מסוים (מיידית)¶
function greet(greeting, punctuation) {
console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}
const alice = { name: "Alice" };
const bob = { name: "Bob" };
greet.call(alice, "Hello", "!"); // "Hello, I'm Alice!"
greet.call(bob, "Hi", "."); // "Hi, I'm Bob."
apply - כמו call, אבל הארגומנטים במערך¶
greet.apply(alice, ["Hello", "!"]); // "Hello, I'm Alice!"
// useful when you have arguments in an array
const args = ["Hey", "!!!"];
greet.apply(bob, args); // "Hey, I'm Bob!!!"
// modern alternative with spread:
greet.call(bob, ...args); // same thing
ההבדלים בקצרה:
- bind - מחזירה פונקציה חדשה, לא קוראת מיד
- call - קוראת מיד, ארגומנטים בנפרד
- apply - קוראת מיד, ארגומנטים במערך
כלל זיכרון: bind = build new function, call = comma separated args, apply = array args
סיכום הכללים¶
| מצב | ערך של this |
|---|---|
מתודה: obj.method() |
obj |
פונקציה רגילה: func() |
undefined (strict) / window |
| פונקציית חץ | this מהסקופ החיצוני |
| event handler (רגיל) | ה-element שקיבל את האירוע |
new Constructor() |
האובייקט החדש שנוצר |
func.call(obj) / func.apply(obj) |
obj |
func.bind(obj)() |
obj |
כללי אצבע:
- מתודות באובייקט - השתמשו בפונקציות רגילות (לא arrow)
- callbacks בתוך מתודות - השתמשו ב-arrow functions
- event handlers - פונקציה רגילה אם רוצים this כ-element, או arrow עם event.target
- מחלקות - bind ב-constructor או arrow functions כ-class fields
השוואה לפייתון¶
| נושא | פייתון | ג׳אווהסקריפט |
|---|---|---|
| הפניה עצמית | self - פרמטר מפורש |
this - מילת מפתח מובנית |
| קביעת ערך | תמיד האובייקט | תלוי בדרך הקריאה |
| איבוד הקשר | לא קורה (self מועבר מפורשות) | קורה כשמעבירים מתודה כ-callback |
| קישור ידני | לא נדרש | bind / call / apply |
| arrow functions | אין מקבילה | יורשות this מהסקופ |
בפייתון, self תמיד מצביע על האובייקט כי הוא מועבר כפרמטר ראשון. ב-JS, this דינמי ויכול להשתנות בהתאם לדרך שבה הפונקציה נקראת. זו הסיבה שלמדנו כלים כמו bind ו-arrow functions - הם פותרים בעיות שפשוט לא קיימות בפייתון.