this, call, apply and bind
Inside a method, the this keyword points to the object the method was called on. An important detail: this is determined at the moment of the call, not when the function was written. So a method passed somewhere separately from its object can "lose" its this. The call, apply and bind methods let you set it explicitly.
this depends on the call
When a function is called through a dot — user.greet() — this inside it equals the object to the left of the dot:
const user = {
name: 'Anna',
greet() {
return `Hi, I am ${this.name}`;
},
};
console.log(user.greet()); // Hi, I am AnnaLosing this
If you store a method in a variable and call it without the object, the link to the object is gone. In classes this then equals undefined, and reading a property from it throws an error:
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hi, I am ${this.name}`;
}
}
const anna = new User('Anna');
const greet = anna.greet;
try {
greet();
} catch (error) {
console.log(error.name); // TypeError
}call
The call() method calls a function right away and sets its this — the first argument. The remaining arguments are passed to the function as usual:
function introduce(city) {
return `${this.name} from ${city}`;
}
const oleg = { name: 'Oleg' };
console.log(introduce.call(oleg, 'Riga')); // Oleg from RigaThis way one function can be used with different objects.
apply
apply() does the same, but takes the function's arguments as an array:
function introduce(city, country) {
return `${this.name}: ${city}, ${country}`;
}
const maria = { name: 'Maria' };
const place = ['Madrid', 'Spain'];
console.log(introduce.apply(maria, place)); // Maria: Madrid, SpainToday apply() is less common: people write introduce.call(maria, ...place) with the spread operator instead.
bind
bind() does not call the function; it returns a new one whose this is fixed for good. You can pass it anywhere:
const user = {
name: 'Anna',
greet() {
return `Hi, I am ${this.name}`;
},
};
const greet = user.greet.bind(user);
console.log(greet()); // Hi, I am Annabind() can also fix the first arguments. If this is not needed, pass null:
const multiply = (a, b) => a * b;
const double = multiply.bind(null, 2);
console.log(double(5)); // 10Arrow functions
An arrow function has no this of its own: it takes it from the surrounding code. So inside a method an arrow sees the same object, and call and bind have no effect on it:
const counter = {
count: 0,
addAll(numbers) {
numbers.forEach(() => {
this.count++;
});
return this.count;
},
};
console.log(counter.addAll([1, 2, 3])); // 3Common mistake
Passing a method as a callback — to a timer or an event handler. setTimeout(timer.tick, 100) passes the function without the object, and this is lost inside tick. Fix it with bind() or wrap the call in an arrow:
class Timer {
constructor() {
this.seconds = 0;
}
tick() {
this.seconds++;
console.log(this.seconds);
}
}
const timer = new Timer();
setTimeout(timer.tick.bind(timer), 100); // 1
setTimeout(() => timer.tick(), 200); // 2