this, call, apply и bind
Внутри метода слово this указывает на объект, у которого метод вызвали. Важная деталь: this определяется в момент вызова, а не когда функцию написали. Поэтому метод, переданный куда-то отдельно от объекта, может «потерять» свой this. Методы call, apply и bind позволяют задать его явно.
this зависит от вызова
Когда функцию вызывают через точку — user.greet(), — this внутри неё равен объекту слева от точки:
const user = {
name: 'Анна',
greet() {
return `Привет, я ${this.name}`;
},
};
console.log(user.greet()); // Привет, я АннаПотеря this
Если сохранить метод в переменную и вызвать без объекта, связь с объектом пропадает. В классах this тогда равен undefined, и обращение к его свойству вызывает ошибку:
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Привет, я ${this.name}`;
}
}
const anna = new User('Анна');
const greet = anna.greet;
try {
greet();
} catch (error) {
console.log(error.name); // TypeError
}call
Метод call() вызывает функцию сразу и задаёт ей this — первым аргументом. Остальные аргументы передаются в функцию как обычно:
function introduce(city) {
return `${this.name} из города ${city}`;
}
const oleg = { name: 'Олег' };
console.log(introduce.call(oleg, 'Рига')); // Олег из города РигаТак одну функцию можно использовать с разными объектами.
apply
apply() делает то же самое, но аргументы функции принимает массивом:
function introduce(city, country) {
return `${this.name}: ${city}, ${country}`;
}
const maria = { name: 'Мария' };
const place = ['Мадрид', 'Испания'];
console.log(introduce.apply(maria, place)); // Мария: Мадрид, ИспанияСегодня apply() встречается реже: вместо него пишут introduce.call(maria, ...place) с оператором spread.
bind
bind() не вызывает функцию, а возвращает новую, у которой this закреплён навсегда. Её можно передавать куда угодно:
const user = {
name: 'Анна',
greet() {
return `Привет, я ${this.name}`;
},
};
const greet = user.greet.bind(user);
console.log(greet()); // Привет, я АннаЧерез bind() можно закрепить и первые аргументы. Если this не нужен, передают null:
const multiply = (a, b) => a * b;
const double = multiply.bind(null, 2);
console.log(double(5)); // 10Стрелочные функции
У стрелочной функции нет своего this: она берёт его из окружающего кода. Поэтому внутри метода стрелка видит тот же объект, а call и bind на неё не действуют:
const counter = {
count: 0,
addAll(numbers) {
numbers.forEach(() => {
this.count++;
});
return this.count;
},
};
console.log(counter.addAll([1, 2, 3])); // 3Частая ошибка
Передать метод как колбэк — в таймер или обработчик события. Запись setTimeout(timer.tick, 100) передаёт функцию без объекта, и внутри tick пропадает this. Закрепите его через bind() или оберните вызов в стрелку:
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