Функции в JavaScript — полноправные объекты. Их можно присваивать в переменные, передавать в другие функции, возвращать из третьих, заносить в массивы, отделять от объектов и звать когда угодно. При такой гибкости движок не может «прошить» this в саму функцию: пришлось бы отказаться от .call(), .bind(), method borrowing и половины остального инструментария. Компромисс дизайна: this определяется в момент вызова, а не в момент описания.
Отсюда все странности. Одна функция может дать четыре разных this, если её вызвать четырьмя способами. Метод объекта, отделённый в переменную, перестаёт видеть свой объект. Стрелочная функция вообще игнорирует все правила и таскает this из внешнего скоупа. Всё это — следствие одного и того же дизайнерского решения.
Ключевая мысль
Значение this определяется не там, где функция описана, а тем, как её вызывают.
Шесть сценариев вызова — простой вызов, метод объекта, .call()/.apply(), .bind(), конструктор через new, стрелочная функция — отличаются только тем, какое из этих «как» распознаёт движок в конкретной строке кода. Никакой магии, только шесть чётких правил.
Стрелочная функция ломает правило
Есть одно исключение — стрелочные функции. У них нет собственного this: он берётся из окружающего скоупа, лексически. Это исключение появилось в ES2015 не случайно — именно чтобы решить проблему потери контекста в callbacks и вложенных функциях, которая была самой частой ошибкой с this в старом коде.
С появлением стрелочных функций и, позже, функциональных React-компонентов проблема this в новом коде встречается заметно реже. Но для чтения существующего кода и понимания языка на уровне выше базового — знание правил про this остаётся обязательным.
Как избегать головной боли
Ключевое правило чтения кода с this: перед оценкой значения посмотри на вызов. Не на функцию, не на её тело, не на класс. Смотри строку, где функция запускается:
- Есть точка перед именем? «obj.method()» → this — obj.
- Стоит new? «new Fn()» → this — новый объект.
- Стоит .call()/.apply()/.bind()? → this — первый аргумент.
- Функция стрелочная? → this из внешнего скоупа; смотри туда.
- Ничего из этого? → простой вызов, this — глобальный объект (или undefined в строгом режиме).
Дальше мануал разбирает все шесть сценариев по одному, с примерами, реальными use cases и типичными ошибками. Читай последовательно или прыгай по нужному разделу — главы независимы.