`this` в JS: контекст и его потеря
Продолжаю серию про базовые вопросы для подготовки к собеседованиям. Следующая тема: this.
Во многих популярных языках this — это чаще всего объект, чей метод был вызван. В JavaScript это работает немного иначе: значение this зависит от того, как именно вызвана функция.
Рассмотрим код:
const user = {
name: "Вася",
greet() {
console.log("Привет,", this.name);
},
};
user.greet(); // Привет, Вася
const greet = user.greet;
greet(); // Привет, undefined
Казалось бы, одна и та же функция одного и того же объекта, и ожидается одинаковый результат, но не всё так просто.
Как работает this
Метод объекта
Когда функция вызывается как метод объекта, this ссылается на этот объект:
user.greet(); // this === user
Обычный вызов
Когда функция вызывается без объекта перед точкой, this теряется. В строгом режиме он будет равен undefined, в нестрогом — будет ссылаться на глобальный объект globalThis (в браузере это window):
const greet = user.greet;
greet(); // this === undefined
Строгий режим активен по умолчанию в ES-модулях и телах классов, а TypeScript добавляет его в скомпилированный код. В современном проекте он есть почти всегда.
Стрелочная функция
Стрелочная функция не имеет собственного this. Она берёт его из той области видимости, где была создана, и его нельзя переопределить через call, apply или bind.
Стрелочные функции удобно использовать в колбэках, когда нужно сохранить this из окружающего кода:
const user = {
name: "Вася",
greet() {
setTimeout(() => {
console.log(this.name); // Вася
}, 1000);
},
};
this внутри setTimeout здесь взят из greet, где this ссылается на user. Если бы внутри setTimeout использовалась обычная функция (function expression), у неё был бы собственный this — и в строгом режиме он был бы undefined.
Стрелочную функцию нельзя использовать как метод объекта, если внутри нужен this.
const user = {
name: "Вася",
greet: () => {
console.log(this.name); // undefined
},
};
Стрелочная функция здесь создана в глобальном контексте, поэтому this будет undefined, а не ссылкой на user. В таком случае нужно использовать обычный метод — shorthand или function expression:
const user = {
name: "Вася",
greet() {
console.log(this.name); // Вася
},
// или
greet: function () {
console.log(this.name); // Вася
},
};
Методы call, apply, bind
Эти три метода позволяют явно задать this для функций.
call вызывает функцию немедленно, принимает аргументы через запятую:
function introduce(greeting, planet) {
console.log(greeting + ", я " + this.name + " с планеты " + planet);
}
const user = { name: "Вася" };
introduce.call(user, "Привет", "Земля"); // Привет, я Вася с планеты Земля
apply делает то же, что и call, но аргументы передаются массивом:
introduce.apply(user, ["Привет", "Земля"]); // Привет, я Вася с планеты Земля
bind не вызывает функцию сразу, а возвращает новую функцию с зафиксированным this:
const boundIntroduce = introduce.bind(user);
boundIntroduce("Привет", "Земля"); // Привет, я Вася с планеты Земля
Как сохранить this
Контекст теряется каждый раз, когда функция вызывается без объекта перед точкой. Вот самые частые сценарии.
Если метод передаётся как колбэк, он теряет связь с объектом:
const user = {
name: "Вася",
greet() {
console.log("Привет,", this.name);
},
};
setTimeout(user.greet, 1000); // ❌ this потерян: Привет, undefined
setTimeout(user.greet.bind(user), 1000); // ✅ bind фиксирует this: Привет, Вася
setTimeout(() => user.greet(), 1000); // ✅ метод вызывается на объекте: Привет, Вася
Если внутри метода нужен this в колбэке, важна разница между функцией, объявленной через function, и стрелочной:
greet() {
setTimeout(function() {
console.log(this.name); // ❌ undefined — у function свой this
}, 1000);
setTimeout(() => {
console.log(this.name); // ✅ Вася — this взят из greet
}, 1000);
}
В старом коде ту же проблему решали через self = this:
greet() {
const self = this;
setTimeout(function() {
console.log(self.name); // ✅ Вася
}, 1000);
}
Стрелочные функции в ES6 сделали этот паттерн ненужным. В старом коде можно встретить self, that или me — суть одна.
Если метод часто передаётся как колбэк, удобнее объявить его как поле класса со стрелочной функцией:
class User {
name = "Вася";
greet = () => {
console.log("Привет,", this.name); // всегда Вася
};
}
const user = new User();
setTimeout(user.greet, 1000); // ✅ Привет, Вася
Каждый экземпляр получает свою копию greet с зафиксированным this — метод можно передавать куда угодно без bind.
Почему это спрашивают на собеседовании
Вопрос про this — это проверка понимания того, как JavaScript определяет контекст вызова функции.
Что нужно знать:
thisопределяется в момент вызова функции, а не в момент её объявления.- У стрелочной функции нет своего
this, она берёт его из внешнего контекста, и переопределить его черезcall,applyилиbindневозможно. callиapplyвызывают функцию немедленно с явнымthis,bindвозвращает новую функцию с зафиксированнымthis.- Потеря контекста при передаче метода как колбэка — классический источник багов.
Понимание того, как работает this, объясняет целый класс багов, которые выглядят не иначе как магия. Чаще всего это понимание приходит через конкретный баг в проде.
← К статьям