У стрелочной функции нет своего 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) устойчивыми к потере контекста.