4.2 מילת המפתח this פתרון
פתרון - מילת המפתח this¶
תרגיל 1 - חידות this¶
חידה א:¶
const obj = {
name: "Object",
getName: function() {
return this.name;
},
getNameArrow: () => {
return this.name;
}
};
console.log(obj.getName()); // "Object"
console.log(obj.getNameArrow()); // undefined
getName- פונקציה רגילה,thisהואobjכי נקראה עםobj.getName()getNameArrow- פונקציית חץ,thisנקבע לפי הסקופ שבו האובייקט הוגדר (הסקופ הגלובלי), לא לפי מי שקרא לה. ב-strict modethis.nameיהיהundefined
חידה ב:¶
const person = {
name: "Alice",
friends: ["Bob", "Charlie"],
listFriends() {
this.friends.forEach(function(friend) {
console.log(`${this.name} is friends with ${friend}`);
});
}
};
person.listFriends();
// "undefined is friends with Bob"
// "undefined is friends with Charlie"
ה-callback הרגיל שמועבר ל-forEach מאבד את this. בתוך ה-callback, this הוא undefined (strict) או window, לא person.
תיקון: להשתמש ב-arrow function ב-callback.
חידה ג:¶
function Dog(name) {
this.name = name;
this.bark = function() {
console.log(`${this.name} says woof!`);
};
}
const dog = new Dog("Rex");
const bark = dog.bark;
dog.bark(); // "Rex says woof!"
bark(); // "undefined says woof!" (or TypeError in strict mode)
dog.bark() - נקראה כמתודה, this הוא dog.
bark() - נקראה כפונקציה רגילה, this הולך לאיבוד.
תרגיל 2 - תיקון הקשר שאבד¶
פתרון עם bind:¶
class Playlist {
constructor(name) {
this.name = name;
this.songs = [];
// bind methods in constructor
this.addSong = this.addSong.bind(this);
this.play = this.play.bind(this);
}
addSong(song) {
this.songs.push(song);
console.log(`Added "${song}" to ${this.name}`);
}
play() {
console.log(`Playing ${this.name}:`);
this.songs.forEach(function(song, i) {
console.log(` ${i + 1}. ${song} (from ${this.name})`);
}.bind(this)); // bind the forEach callback too
}
}
פתרון עם arrow functions:¶
class Playlist {
constructor(name) {
this.name = name;
this.songs = [];
}
// arrow function as class field - 'this' is always the instance
addSong = (song) => {
this.songs.push(song);
console.log(`Added "${song}" to ${this.name}`);
};
play = () => {
console.log(`Playing ${this.name}:`);
// arrow callback inherits 'this'
this.songs.forEach((song, i) => {
console.log(` ${i + 1}. ${song} (from ${this.name})`);
});
};
}
const playlist = new Playlist("My Favorites");
playlist.addSong("Song A");
playlist.addSong("Song B");
playlist.play();
const addSong = playlist.addSong;
addSong("Song C"); // works
setTimeout(playlist.play, 100); // works
תרגיל 3 - call, apply, bind¶
חלק א:¶
function introduce(greeting, hobby) {
console.log(`${greeting}! I'm ${this.name}, age ${this.age}. I love ${hobby}.`);
}
const alice = { name: "Alice", age: 25 };
const bob = { name: "Bob", age: 30 };
// using call - arguments separated by commas
introduce.call(alice, "Hello", "coding");
// "Hello! I'm Alice, age 25. I love coding."
introduce.call(bob, "Hi", "gaming");
// "Hi! I'm Bob, age 30. I love gaming."
// using apply - arguments in an array
introduce.apply(alice, ["Hey", "reading"]);
// "Hey! I'm Alice, age 25. I love reading."
introduce.apply(bob, ["Yo", "cooking"]);
// "Yo! I'm Bob, age 30. I love cooking."
חלק ב:¶
function borrowMethod(source, methodName, target) {
return source[methodName].bind(target);
}
const calculator = {
value: 0,
add(n) {
this.value += n;
return this;
},
getValue() {
return this.value;
}
};
const myObj = { value: 100 };
const boundAdd = borrowMethod(calculator, "add", myObj);
boundAdd(5);
boundAdd(10);
console.log(myObj.value); // 115
תרגיל 4 - אובייקט עם שרשור מתודות¶
class QueryBuilder {
constructor(table) {
this.table = table;
this.fields = ["*"];
this.conditions = [];
this.order = null;
this.limitCount = null;
}
select(...fields) {
this.fields = fields;
return this;
}
where(field, operator, value) {
const formattedValue = typeof value === "string" ? `'${value}'` : value;
this.conditions.push(`${field} ${operator} ${formattedValue}`);
return this;
}
orderBy(field) {
this.order = field;
return this;
}
limit(count) {
this.limitCount = count;
return this;
}
build() {
let query = `SELECT ${this.fields.join(", ")} FROM ${this.table}`;
if (this.conditions.length > 0) {
query += ` WHERE ${this.conditions.join(" AND ")}`;
}
if (this.order) {
query += ` ORDER BY ${this.order}`;
}
if (this.limitCount !== null) {
query += ` LIMIT ${this.limitCount}`;
}
return query;
}
}
// test
const query = new QueryBuilder("users")
.select("name", "email")
.where("age", ">", 18)
.where("active", "=", true)
.orderBy("name")
.limit(10)
.build();
console.log(query);
// "SELECT name, email FROM users WHERE age > 18 AND active = true ORDER BY name LIMIT 10"
כל מתודה מחזירה this - מה שמאפשר שרשור. build מחזירה מחרוזת כי היא סוף השרשרת.
תרגיל 5 - this ב-event handlers¶
class ClickTracker {
constructor(selector) {
this.element = document.querySelector(selector);
this.count = 0;
// bind the handler so we can remove it later
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.count++;
}
start() {
this.element.addEventListener("click", this.handleClick);
}
stop() {
this.element.removeEventListener("click", this.handleClick);
}
getCount() {
return this.count;
}
reset() {
this.count = 0;
}
}
// test
const tracker = new ClickTracker("#myButton");
tracker.start();
// click 3 times...
console.log(tracker.getCount()); // 3
tracker.stop();
tracker.reset();
console.log(tracker.getCount()); // 0
נקודות חשובות:
- עושים bind ב-constructor כדי ש-handleClick תמיד תצביע על ה-instance
- שומרים הפניה לאותה פונקציה bound כדי ש-removeEventListener יעבוד (צריך את אותו reference בדיוק)
תרגיל 6 - this במקרים מורכבים¶
חלק א + ב:¶
function createEventEmitter() {
const events = {};
return {
on(event, handler) {
if (!events[event]) {
events[event] = [];
}
events[event].push(handler);
},
emit(event, ...args) {
if (!events[event]) return;
events[event].forEach((handler) => {
// use call to set 'this' to the emitter
handler.call(this, ...args);
});
},
off(event, handler) {
if (!events[event]) return;
events[event] = events[event].filter(h => h !== handler);
},
once(event, handler) {
const wrapper = (...args) => {
handler.call(this, ...args);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
};
}
// test
const emitter = createEventEmitter();
function onData(data) {
console.log(`${this.name} received: ${data}`);
}
emitter.name = "MyEmitter";
emitter.on("data", onData);
emitter.emit("data", "hello"); // "MyEmitter received: hello"
// test once
emitter.once("login", function(user) {
console.log(`${this.name}: ${user} logged in`);
});
emitter.emit("login", "Alice"); // "MyEmitter: Alice logged in"
emitter.emit("login", "Bob"); // (nothing - handler was removed)
שימו לב ש-emit משתמש ב-handler.call(this, ...args) כדי שה-this בתוך ה-handler יהיה ה-emitter. ב-once, ה-wrapper הוא arrow function כדי שה-this שלו יהיה ה-emitter (ירושה מ-once).
תרגיל 7 - השוואה מסכמת¶
const obj = {
value: 42,
regular() {
console.log(this.value); // (A)
const inner = function() {
console.log(this.value); // (B)
};
inner();
const arrow = () => {
console.log(this.value); // (C)
};
arrow();
},
arrow: () => {
console.log(this.value); // (D)
}
};
קריאה obj.regular():¶
- (A) -
42-thisהואobjכי נקראה כמתודה - (B) -
undefined-innerנקראה כפונקציה רגילה,thisהואundefined(strict) אוwindow - (C) -
42- arrow function יורשתthisמ-regular, שםthisהואobj
קריאה obj.arrow():¶
- (D) -
undefined- arrow function יורשתthisמהסקופ שבו האובייקט הוגדר (גלובלי), לא מ-obj
קריאה fn() (כש-fn = obj.regular):¶
- (E-A) -
undefined- נקראה כפונקציה רגילה,thisהולך לאיבוד - (E-B) -
undefined- פונקציה רגילה בתוך פונקציה רגילה - (E-C) -
undefined- arrow יורשתthisמ-regular, ושםthisהואundefined
קריאה bound() (אחרי bind(obj)):¶
- (F-A) -
42-bindקובע ש-thisהואobj - (F-B) -
undefined-innerעדיין פונקציה רגילה שנקראת לבד,bindלא משפיע על פונקציות פנימיות - (F-C) -
42- arrow יורשתthisמ-regular, ושםthisהואobj(בזכות ה-bind)