Всё, что метод получает от «принадлежности» объекту, держится ровно на одной синтаксической детали — на точке в вызове. Если точка есть (obj.method()), this внутри указывает на объект. Если точки нет, а метод куда-то передан или скопирован в переменную — связь моментально разрывается, и функция ведёт себя как самостоятельная. Именно на этой хрупкости построено большинство багов с потерянным this в реальных проектах.

Ниже — три типовых способа «случайно» отделить метод, что с ним происходит, и три инструмента для восстановления контекста.

Сценарий 1: присваивание в переменную

const timer = {
  seconds: 0,
  tick() {
    this.seconds += 1;
    console.log(this.seconds);
  }
};

timer.tick();   // => 1  (this = timer)

// а теперь оторвали метод:
const runTick = timer.tick;
runTick();      // TypeError: Cannot read properties of undefined

Строчка const runTick = timer.tick копирует ссылку на функцию, но не «прикрепление» к объекту. Последующий runTick() — это уже простой вызов; this внутри — глобальный объект или undefined в строгом режиме (см. подглаву 2.1).

Сценарий 2: метод как callback

Самый частый способ незаметно отделить метод — передать его в другую функцию как аргумент:

class Logger {
  constructor(prefix) {
    this.prefix = prefix;
  }
  log(message) {
    console.log(`${this.prefix}: ${message}`);
  }
}

const errorLog = new Logger('ERROR');

// не работает — this внутри log теряется
setTimeout(errorLog.log, 100, 'network failed');
// или так — тоже не работает
button.addEventListener('click', errorLog.log);
// или в массиве
['ready', 'set', 'go'].forEach(errorLog.log);

Во всех трёх строках JavaScript получает ссылку на метод log, но не на объект errorLog. Когда движок вызовет функцию через setTimeout / event handler / forEach, это будет простой вызов, и this.prefix внутри упадёт с ошибкой.

Сценарий 3: destructuring и деструктурированное присваивание

Более коварный вариант — когда метод «случайно» отделяется через destructuring:

const api = {
  baseUrl: 'https://api.example.com',
  fetch(path) {
    return globalThis.fetch(`${this.baseUrl}${path}`);
  }
};

// сработает
api.fetch('/users');

// не сработает — метод отделён
const { fetch } = api;
fetch('/users');   // TypeError: this.baseUrl is undefined

Destructuring — удобный синтаксис для извлечения полей, но с методами он превращается в ловушку: получаешь голую функцию без объекта. Всё то же, что const fetch = api.fetch, только визуально короче.

Способ 1: .bind()

Первый способ — связать метод с его объектом навсегда через .bind() (полный разбор в главе 6):

const boundTick = timer.tick.bind(timer);
boundTick();   // => 2 (this внутри — timer)

// или сразу при передаче
setTimeout(errorLog.log.bind(errorLog), 100, 'network failed');

Метод получает контекст на этапе создания привязки, а не в момент вызова. Даже переданный дальше как callback, метод сохранит правильный this.

Способ 2: стрелочная функция-обёртка

Второй способ — обернуть вызов в стрелочную функцию (см. главу 7):

setTimeout(() => errorLog.log('network failed'), 100);

button.addEventListener('click', () => errorLog.log('button clicked'));

['ready', 'set', 'go'].forEach(msg => errorLog.log(msg));

Стрелочная функция не отделяет метод — внутри неё errorLog.log(...) остаётся полноценным вызовом метода со всей точкой-владельцем. Это самый частый способ решить проблему в современном коде.

Способ 3: class fields с arrow-функциями

Третий способ применим только для классов — определить метод как class-field со стрелочной функцией:

class Logger {
  constructor(prefix) {
    this.prefix = prefix;
  }

  // обычный метод — этот this можно потерять
  log(message) {
    console.log(`${this.prefix}: ${message}`);
  }

  // стрелочная в class-field — this зафиксирован намертво
  logSafe = (message) => {
    console.log(`${this.prefix}: ${message}`);
  };
}

const errorLog = new Logger('ERROR');

// сработает без .bind() и без обёртки
setTimeout(errorLog.logSafe, 100, 'still works');

Class fields (стандартизированы в ES2022) выполняются в момент создания экземпляра, и стрелочная функция там «захватывает» this лексически. Такой метод фактически хранится как свойство экземпляра, а не как метод прототипа — отделить его от контекста уже невозможно.

React-контекст: до и после hooks

Ключевая история про this в веб-разработке связана с React. До появления hooks (2019 год) компоненты писались как классы, и передача методов в event handlers была нормой:

class TodoItem extends React.Component {
  constructor(props) {
    super(props);
    // так делали в 2016-2018: bind в конструкторе
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.props.onDelete(this.props.id);
  }

  render() {
    return <li onClick={this.handleClick}>{this.props.text}</li>;
  }
}

Позже появилась идея использовать class fields с arrow-функциями — и bind в конструкторе стал не нужен:

class TodoItem extends React.Component {
  // class field — this зафиксирован
  handleClick = () => {
    this.props.onDelete(this.props.id);
  };

  render() {
    return <li onClick={this.handleClick}>{this.props.text}</li>;
  }
}

После введения hooks (React 16.8, февраль 2019) функциональные компоненты стали дефолтом, и вопрос про this в React-коде практически исчез. Но старые кодовые базы с классами всё ещё живут — в них паттерн .bind(this) в конструкторе или class-field-arrow остаётся частой встречей.

Как выбирать способ

  • Стрелочная обёртка — дефолт для одноразовой передачи метода как callback. Просто, читабельно, работает везде.
  • .bind() — когда метод передают многократно и хочется один раз зафиксировать контекст. Или когда нужна именно «связанная функция» как ссылка для последующего removeEventListener.
  • Class fields с arrow — для классов, где method-вызов делается через event handlers сотнями раз. Лучший из способов, но работает только в классах и создаёт метод на экземпляр, а не на прототип.

В функциональных React-компонентах и других современных фреймворках проблема отделения метода не возникает вовсе — функциональные компоненты не имеют собственного this. Это одна из причин, почему индустрия ушла от классовых компонентов.