Урок по треку JavaScript 6 мин

this, call, apply, bind за 6 минут

Четыре правила определения this, стрелки и потеря контекста.

this в JavaScript определяется не там, где функция написана, а тем, как она вызвана. Это единственное «динамическое» место в лексическом языке, и поэтому на нём ловят почти всех.

Четыре правила для обычных функций

Смотрим на место вызова и проверяем правила по приоритету сверху вниз.

1. Вызов через new. this это новый созданный объект.

function User(name) { this.name = name; }
const u = new User('Аня'); // this === u

2. Явная привязка: call, apply, bind. this это переданный первый аргумент.

function greet(greeting) { return `${greeting}, ${this.name}`; }
greet.call({ name: 'Аня' }, 'Привет');     // 'Привет, Аня'
greet.apply({ name: 'Оля' }, ['Здравствуй']); // аргументы массивом
const g = greet.bind({ name: 'Катя' });     // новая функция с жёстким this
g('Хай');                                   // 'Хай, Катя'

3. Вызов как метод: obj.method(). this это объект слева от точки в момент вызова.

4. Обычный вызов fn(). В строгом режиме (модули, классы, 'use strict') this это undefined. В нестрогом скрипте это глобальный объект (window в браузере).

Потеря контекста

Метод это просто функция, лежащая в свойстве. Как только вы её достали и вызвали отдельно, правило 3 перестаёт работать:

const user = {
  name: 'Аня',
  hi() { return this.name; },
};
const hi = user.hi;
hi();                       // в строгом режиме TypeError: this === undefined
setTimeout(user.hi, 0);     // тоже потеря: таймер вызывает функцию без объекта
button.addEventListener('click', user.hi); // this будет button, а не user

Лечится тремя способами: user.hi.bind(user), обёртка () => user.hi() или стрелка внутри метода.

Стрелочные функции

У стрелок нет своего this. Они берут this из окружающей области в момент создания, как обычную переменную. Поэтому:

  • call, apply, bind не меняют this стрелки;
  • стрелку нельзя вызвать через new;
  • стрелка как метод объекта почти всегда ошибка: this будет внешним, а не объектом.
const timer = {
  seconds: 0,
  start() {
    setInterval(() => { this.seconds++; }, 1000); // this === timer
  },
};

Здесь стрелка внутри метода как раз нужна: она берёт this из start, а start вызван как метод.

bind подробнее

  • bind возвращает новую функцию, оригинал не меняется.
  • Повторный bind на уже привязанной функции this не меняет: побеждает первая привязка.
  • bind умеет частичное применение: const double = mul.bind(null, 2).
  • Привязанная функция, вызванная через new, игнорирует привязанный this: new сильнее.

this в классах

Тело класса всегда в строгом режиме. Метод, переданный как колбэк, теряет this и получает undefined. Частые решения:

class Counter {
  count = 0;
  inc = () => { this.count++; }; // поле-стрелка: своя копия на экземпляр
  dec() { this.count--; }
}

Поле-стрелка удобно для обработчиков, но создаётся на каждый экземпляр и не лежит в прототипе.

this в обработчиках DOM

В обычной функции-обработчике this равен event.currentTarget, то есть элементу, на котором висит слушатель. В стрелке this внешний.

call против apply

Делают одно и то же, различаются передачей аргументов: call(ctx, a, b) и apply(ctx, [a, b]). Сейчас вместо apply чаще пишут fn.call(ctx, ...args) или просто spread.

Частые ошибки

  • Стрелка как метод объекта и ожидание, что this это объект.
  • Передача метода класса в колбэк без bind.
  • Ожидание, что bind можно «перепривязать».
  • Уверенность, что this зависит от места объявления. Это верно только для стрелок.

Что спросят на собесе

  • Как определяется this, назовите правила и их приоритет.
  • Чем call, apply и bind отличаются друг от друга.
  • Почему метод теряет контекст при передаче в setTimeout или обработчик и как это исправить.
  • Чем this в стрелке отличается от обычной функции.
  • Реализуйте свой bind.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.