this depends on HOW it is called
The most important rule about this: it does not matter where you defined the
function, it matters how you invoke it. Think of this as an empty seat filled
in at call time, based on whatever is "to the left of the dot".
const person = {
name: "Ana",
greet() {
return "Hi, I'm " + this.name;
},
};
person.greet(); // this = person -> "Hi, I'm Ana"
const loose = person.greet;
loose(); // this = undefined -> error or "undefined"!
When you store the method in loose and call it "bare", there is no longer
anything to the left of the dot: this is lost. This is the dreaded
"loss of this".
Where it bites you in practice
It happens whenever you pass a method as a callback and other code calls it for you:
setTimeout(person.greet, 100); // this is lost
button.addEventListener("click", obj.handle); // this is lost
const { greet } = person; // destructuring also detaches it
bind: pins this and returns a new function
bind does not call the function: it creates a bound copy tied to a fixed
this that never changes again, even if you invoke it bare.
const greetAna = person.greet.bind(person);
greetAna(); // "Hi, I'm Ana" (even when called bare)
setTimeout(greetAna, 100); // now it works
call and apply: invoke NOW while pinning this
Unlike bind, these run the function immediately while indicating which
this to use. The only difference between them is how you pass the arguments:
call: loose arguments, separated by commas.apply: arguments inside an array.
function introduce(greeting, mark) {
return greeting + ", I'm " + this.name + mark;
}
const ana = { name: "Ana" };
introduce.call(ana, "Hi", "!"); // call: loose args
introduce.apply(ana, ["Hi", "!"]); // apply: args in array
// Both -> "Hi, I'm Ana!"
Trick to remember: apply uses an array.
Modern solution: class fields with arrow functions
Arrow functions do not have their own this: they inherit it from where
they were created. If you define a method as a field with an arrow, it stays
"welded" to the instance and never loses this, even if you pass it as a
callback:
class Button {
constructor(text) {
this.text = text;
}
// class field with arrow: this will always be this instance
click = () => {
return "Pressed: " + this.text;
};
}
const b = new Button("Accept");
const fn = b.click;
fn(); // "Pressed: Accept" (this is not lost)
Examples
The loss of this and how bind fixes it
const counter = {
value: 10,
read() {
return this.value;
},
};
const loose = counter.read;
const bound = counter.read.bind(counter);
console.log("Bound:", bound()); // 10
console.log("Loose this:", loose === counter.read);
call vs apply: same function, different way to pass args
function range(min, max) {
return this.name + " between " + min + " and " + max;
}
const sensor = { name: "Temperature" };
console.log(range.call(sensor, 0, 100));
console.log(range.apply(sensor, [0, 100]));
Arrow in a class field: this welded to the instance
class Timer {
seconds = 0;
tick = () => {
this.seconds++;
return this.seconds;
};
}
const t = new Timer();
const fn = t.tick; // we "detach" it
console.log(fn()); // 1 (still works)
console.log(fn()); // 2