ЯдроКодаподготовка к экзаменам
Учебная платформа

Загружаем материалы

Подготавливаем материалы и навигацию по разделу.

this в JavaScript: вызов метода, стрелки и bind

Автор: · Обновлено

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

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

Среда важна для результата

Запускайте основной пример как файл this.mjs командой node this.mjs в Node.js 22 или новее. Расширение задаёт ES module, где действует строгий режим. В браузере используйте модульный скрипт, если повторяете пример вне консоли. У старого обычного скрипта, CommonJS и модуля есть различия в верхнеуровневом this; без указания среды обсуждение «чему равно this» часто превращается в спор о разных программах.

В предыдущем уроке функция читала имя из лексического окружения. Здесь метод читает поле через this. Это позволяет использовать одну функцию с разными объектами, но требует следить за точкой вызова. Запись object.method() и запись const fn = object.method; fn() вызывают то же значение функции разными способами. Вторая не хранит скрытую память о выражении слева от точки.

Полный пример: один метод, разные получатели

const course = {
  title: 'JavaScript',
  label(prefix) { return prefix + this.title; },
};
const other = { title: 'TypeScript', label: course.label };
const detached = course.label;
const fixed = course.label.bind(course, 'Курс: ');

console.log(course.label('Курс: '));
console.log(other.label('Курс: '));
console.log(detached.call({ title: 'DOM' }, 'Курс: '));
console.log(fixed());
try {
  detached('Курс: ');
} catch (error) {
  console.log(error.name);
}

Вывод по порядку: Курс: JavaScript, Курс: TypeScript, Курс: DOM, Курс: JavaScript, TypeError. В последнем вызове this равен undefined, и чтение поля невозможно. Сообщение ошибки может отличаться между движками, поэтому проверяем класс ошибки, а не дословную формулировку. Второй объект не копировал тело метода: он хранит ту же функцию и задаёт другого получателя при вызове.

call вызывает функцию сразу с явно переданным this. apply решает ту же задачу, но принимает аргументы массивоподобным набором. bind возвращает новую функцию для будущих вызовов; первый блок также заранее закрепляет префикс. Эти операции не меняют исходную функцию и не заменяют её в объекте автоматически.

Как читать точку вызова

Для обычного метода начните с выражения непосредственно перед скобками вызова. В course.label() получатель — course; в other.label()other. Если метод извлекли деструктуризацией, получилась самостоятельная переменная с функцией. Важно различать извлечение значения и вызов через свойство. Круглые скобки вокруг самого выражения доступа обычно не являются потерей получателя; диагностируйте реальную операцию, а не внешний вид строки.

Для вызова через new создаётся контекст конструирования. Это отдельное правило, поэтому механическое «слева от точки» не описывает весь язык. Для привязанной функции обычный новый call не переопределяет закреплённый this; при конструировании действуют правила конструктора. В прикладной работе полезно сначала классифицировать вызов: метод, свободная функция, явная привязка или конструктор.

Не исправляйте проблему подстановкой globalThis. Так код может перестать падать, но начать читать или записывать состояние не того объекта. В нестрогом режиме некоторые свободные вызовы действительно получают глобальный объект; это объяснение исторического поведения, а не подходящая опора для нового кода. Модульный пример специально делает потерю контекста заметной.

Стрелка наследует this

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

const tracker = {
  prefix: 'Шаг ',
  makeFormatter() {
    return (number) => this.prefix + number;
  },
};
const format = tracker.makeFormatter();
console.log(format(2));
console.log(format.call({ prefix: 'Другой ' }, 3));

Ожидаются Шаг 2 и Шаг 3. Стрелка создана во время вызова метода с получателем tracker. Поздний call не заменяет её лексический this. При этом значение поля prefix читается при вызове: если поле объекта изменится, форматирование изменится тоже. Наследование получателя не означает снимок всех его свойств.

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

Callback и снятие подписок

Когда API получает callback, именно его контракт определяет вызов. Нельзя переносить правила DOM-обработчика на таймер Node.js или на стороннюю библиотеку. В браузерном обработчике удобно явно читать event.currentTarget, если нужен элемент, на котором установлена подписка. Если требуется состояние своего объекта, передайте сохранённую привязанную функцию или обёртку, замыкающую объект.

Каждый вызов bind создаёт новую функцию. Поэтому removeEventListener('click', handler.bind(model)) не удалит ранее добавленный результат другого вызова bind. Сохраните ссылку на готовый callback и используйте её при очистке. То же относится к двум внешне одинаковым стрелкам: одинаковый текст не делает их одним объектом функции. Это одновременно вопрос корректности и времени жизни удерживаемых данных.

Практика: безопасно передать метод

Нужно передать функцию в простой исполнитель, который вызывает её без получателя. Состояние должно увеличиться на два, а исходный объект остаться единственным владельцем числа. Решите задачу через сохранённую привязку; альтернативно можно создать стрелочную обёртку с явным вызовом counter.add(2).

function execute(callback) { return callback(); }
const counter = {
  value: 0,
  add(amount) { this.value += amount; return this.value; },
};
const addTwo = counter.add.bind(counter, 2);
console.log(execute(addTwo));
console.log(counter.value);

Вывод: 2 и 2. Если передать counter.add напрямую, исполнитель не сохранит объект и не передаст размер увеличения. Привязка решает обе части контракта: получателя и заранее известный аргумент. Проверьте повторный вызов: результат должен стать четырьмя, что подтверждает работу с тем же состоянием.

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

Почему деструктуризация сломала метод?

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

Почему bind не влияет на стрелку?

У стрелки нет собственного динамического this, который эта операция могла бы заменить. Исправляйте место создания стрелки или передавайте данные параметром.

Как быстро найти потерю контекста?

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

Источники