У стрелочной функции нет своего this. Это — главное отличие от обычной функции и то, ради чего стрелочные вообще были введены в ES2015. Когда внутри стрелочной функции пишешь this, движок JavaScript не создаёт новый контекст, а поднимается по цепочке областей видимости и берёт this из места, где стрелочная функция была написана.
this внутри стрелочной функции определяется лексически — берётся из окружающего скоупа. Не имеет значения, как эту функцию вызвали.
Разбор на конкретном примере
class ShoppingCart {
constructor() {
this.items = [];
this.currency = '₽';
}
formatTotal() {
// стрелочная — this наследуется от formatTotal()
const prices = this.items.map(item => {
return `${item.price} ${this.currency}`; // this — cart
});
return prices.join(', ');
}
}
const cart = new ShoppingCart();
cart.items = [{ price: 500 }, { price: 1200 }];
cart.formatTotal(); // '500 ₽, 1200 ₽'
Стрелочная функция внутри .map() написана в теле метода formatTotal. В этом методе this — экземпляр ShoppingCart (стандартное поведение метода объекта, см. подглаву 3.1). Стрелочная забирает этот this из окружающего скоупа и держит его при каждом обращении к this.currency.
Когда .map() внутри своего цикла зовёт callback, для JavaScript это простой вызов функции. Не будь функция стрелочной, её this оказался бы undefined (тело класса всегда в строгом режиме), и обращение к this.currency упало бы с TypeError.
Сравнение с обычной функцией
Тот же пример с обычной функцией — и всё ломается:
class BrokenCart {
constructor() {
this.items = [];
this.currency = '₽';
}
formatTotal() {
// обычная — this в момент вызова = undefined (строгий режим класса)
return this.items.map(function(item) {
return `${item.price} ${this.currency}`; // TypeError!
}).join(', ');
}
}
Разница — ровно одно слово: function(item) вместо item =>. У обычной функции есть свой this, который определяется в момент вызова. .map() зовёт callback без объекта-владельца — и this внутри становится undefined в строгом режиме (или window в нестрогом).
Определение vs вызов
Ключевая мысль про стрелочный this: он определяется в момент определения функции, не в момент вызова. Как только стрелочная функция создана, её this «прошит» в неё намертво.
const outer = () => {
const inner = () => this;
return inner();
};
// this внутри outer — тот, что был при определении outer
// это глобальный this (window в браузере или undefined в модуле)
outer(); // возвращает window или undefined
Обе функции — и outer, и inner — стрелочные. Они «поднимаются» по скоупу и оба находят this в глобальной области. Значение — глобальный объект или undefined.
call, apply, bind не работают
Так как своего this нет, попытки его переопределить методами .call(), .apply(), .bind() просто игнорируются:
const arrow = () => this;
const target = { name: 'target' };
arrow.call(target); // не сработало — вернёт лексический this
arrow.apply(target); // тоже не сработало
const bound = arrow.bind(target);
bound(); // тоже не сработало
Это по дизайну: стрелочная функция никогда не должна «удивлять» со своим контекстом. Её this предсказуемо совпадает с тем, что был в момент определения.
Не работает как метод объекта
Обратная сторона той же медали: стрелочная функция не подходит как метод объекта. Её this будет НЕ этот объект. Разбор с примером — в следующей подглаве.
Практический итог
Правило прочтения кода: если функция стрелочная, this внутри — тот, который в окружающей области видимости. Ищи его снаружи, не гадай, как функцию позовут.
Правило написания кода: используй стрелочные функции везде, где хочешь «сохранить» this из окружения. Это делает callbacks, event handlers и функции в цепочках методов (map/filter/reduce) устойчивыми к потере контекста.