this в JavaScript: четыре правила связывания

Содержание

О проверке кода. Примеры не запускались — в отличие от материалов по Node.js с фактическим выводом. Говорим прямо, а не ставим бейдж «проверено».

this — самая скользкая тема в JavaScript. Причина путаницы в одном: this не про то, где функция написана, а про то, как её вызвали. Разберём по правилам.

Главный принцип

Значение this определяется в момент вызова, а не в момент объявления. Одна и та же функция при разных вызовах получит разный this. Поэтому смотреть надо не на код функции, а на строку, где её зовут.

Есть ровно четыре правила, и они проверяются по порядку.

Правило 1: вызов как метод

const user = {
  name: 'Аня',
  greet() {
    return `Привет, ${this.name}`;   // this — это user
  },
};

user.greet();   // "Привет, Аня"

Функцию вызвали через точку (user.greet()) — this равен объекту слева от точки. Это самый интуитивный случай: метод знает, к какому объекту он привязан.

Правило 2: вызов сам по себе

function show() {
  return this;
}

show();   // undefined в строгом режиме (глобальный объект в нестрогом)

Функцию вызвали просто по имени, без объекта — this равен undefinedстрогом режиме и в модулях). В нестрогом режиме это глобальный объект, но полагаться на это не стоит.

Именно отсюда растёт «потеря контекста»:

const user = {
  name: 'Аня',
  greet() { return `Привет, ${this.name}`; },
};

const fn = user.greet;   // оторвали метод от объекта
fn();                    // ❌ TypeError: this.name — this это undefined!

Метод оторвали от объекта и вызвали сам по себе — связь потеряна. То же происходит, когда метод передают колбэком.

Правило 3: вызов через new

function User(name) {
  this.name = name;   // this — новый пустой объект
}

const u = new User('Борис');
u.name;   // "Борис"

Вызов через new создаёт новый объект и делает его this. На этом построены конструкторы и классы — там this внутри constructor это создаваемый экземпляр.

Правило 4: явная привязка

function greet() { return `Привет, ${this.name}`; }
const user = { name: 'Вера' };

greet.call(user);        // "Привет, Вера" — вызвать с this = user
greet.apply(user);       // то же, аргументы массивом
const bound = greet.bind(user);   // НЕ вызывает, возвращает функцию с this = user
bound();                 // "Привет, Вера"

call, apply, bind задают this вручную:

  • call — вызвать сейчас, this первым аргументом, остальные через запятую;
  • apply — то же, но аргументы массивом;
  • bind — не вызывать, а вернуть новую функцию с намертво привязанным this.

bind — классическое решение потери контекста: привязали метод к объекту раз и передавайте куда угодно.

Как определить this: проверяйте по порядку
  1. 1 Вызов через new?this = новый создаваемый объект
  2. 2 Через call/apply/bind?this = то, что передали явно
  3. 3 Через точку obj.method()?this = объект слева от точки
  4. 4 Просто fn()?this = undefined (строгий режим)

Стрелочные функции: своего this нет

Пятый случай, который на самом деле отменяет остальные четыре. У стрелочной функции нет собственного this — она берёт его из окружающего кода, где объявлена, и никакой вызов это не меняет.

const timer = {
  seconds: 0,
  start() {
    // ❌ обычная функция: свой this, здесь undefined
    setInterval(function () {
      this.seconds++;   // this — НЕ timer!
    }, 1000);

    // ✅ стрелка: this взят из start(), это timer
    setInterval(() => {
      this.seconds++;
    }, 1000);
  },
};

В колбэке обычная функция вызывается «сама по себе» (правило 2), и this теряется. Стрелка же не имеет своего this — она использует this метода start, то есть timer. До появления стрелок это лечили строкой const self = this — её до сих пор видно в старом коде.

Обратная сторона: стрелка не годится как метод объекта. this внутри неё будет не объектом, а тем, что снаружи:

const user = {
  name: 'Аня',
  greet: () => `Привет, ${this.name}`,   // ❌ this — не user!
};

Практическое правило

Свести всё к простому выбору:

  • метод объекта — обычная функция (method() {}), this будет объектом;
  • колбэк внутри метода — стрелка, чтобы сохранить this;
  • передаёте метод куда-тоbind, чтобы this не потерялся;
  • сомневаетесь — посмотрите на строку вызова, не на объявление.

В современном коде с классами и хуками эта боль почти ушла: методы вызываются как методы, колбэки пишутся стрелками. Но понимать, откуда берётся this, всё равно нужно — иначе очередной undefined в колбэке останется загадкой.


Смежные темы: функции — обычные против стрелочных; классы — где this это экземпляр; прототипы. Полный список — в уроках JavaScript.

Частые вопросы

Как понять, чему равен this?
Смотрите на то, КАК вызвана функция, а не где объявлена. Вызвана как метод объекта — this это объект. Вызвана сама по себе — undefined в строгом режиме. Через new — новый объект. Через call/apply/bind — то, что передали.
Почему this теряется в колбэке?
Потому что колбэк вызывается не как метод вашего объекта, а сам по себе — таймером, обработчиком события, методом массива. В момент вызова связь с объектом потеряна, и this становится undefined. Решение — стрелочная функция или bind.
У стрелочной функции есть свой this?
Нет, и это её главное свойство. Стрелка берёт this из окружающего кода, где объявлена, и не меняет его при вызове. Поэтому она идеальна для колбэков и не годится как метод объекта.
Чем call отличается от bind?
call вызывает функцию сразу с указанным this. bind не вызывает, а возвращает новую функцию с намертво привязанным this — её можно вызвать позже. apply как call, но аргументы массивом.
Чему равен this в обычной функции вне объекта?
В строгом режиме (и в модулях) — undefined. В нестрогом — глобальному объекту. Полагаться на второе не стоит: пишите в строгом режиме, где обращение к this.x на undefined честно упадёт.