Skip to content
Иван Ткаченко Блог
EN
`this` в JS: контекст и его потеря — Иван Ткаченко ← К статьям
JavaScript

`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, объясняет целый класс багов, которые выглядят не иначе как магия. Чаще всего это понимание приходит через конкретный баг в проде.