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.