Стрелочная функция (arrow function) появилась в ES2015 и стала одним из самых заметных синтаксических изменений языка. У неё две ключевые особенности: сокращённая запись и — главное для темы этого мануала — лексическая привязка this. Именно вторая особенность делает стрелочные функции решением большинства проблем с потерянным контекстом.

Синтаксис

// полный вид: обычная функция
const hello = function(name) {
  return 'Привет ' + name;
};

// стрелочный вид
const hello = (name) => {
  return 'Привет ' + name;
};

// если тело — одно выражение, можно опустить фигурные скобки и return
const hello = (name) => 'Привет ' + name;

// если аргумент один — можно опустить круглые скобки
const hello = name => 'Привет ' + name;

// но: если аргументов нет или их несколько — скобки обязательны
const hi = () => 'Привет';
const sum = (a, b) => a + b;

Практический use case, где стрелочные функции выиграли рынок — передача в методы массивов и других функциональных API:

// Оставить только чётные числа
[1, 2, 3, 4, 5, 6].filter(item => item % 2 === 0);   // => [2, 4, 6]

// Удвоить каждый элемент
[1, 2, 3].map(x => x * 2);                            // => [2, 4, 6]

// Просуммировать
[1, 2, 3, 4].reduce((acc, n) => acc + n, 0);          // => 10

По сравнению с длинными function(item) { return item.something; } получается заметно чище. Особенно когда callback короткий — одна строка вместо трёх.

Главная особенность: нет собственного this

Ключевое отличие стрелочной функции от обычной — у неё нет своего this. Она «наследует» его лексически, то есть берёт из внешней области видимости, где была объявлена. Причём из той области, где стрелочная функция ФИЗИЧЕСКИ написана в коде, а не оттуда, откуда её позовут.

const user = {
  name: 'Аня',
  // обычный метод — this = user при вызове
  greetNormal() {
    return `Привет, ${this.name}`;
  },
  // стрелочная — this наследуется от места объявления (не от user!)
  greetArrow: () => `Привет, ${this.name}`,
};

user.greetNormal();    // => 'Привет, Аня'
user.greetArrow();     // => 'Привет, undefined' (this — window, там нет name)

Первый метод — обычная функция, при вызове через user.greetNormal() контекст становится user (см. главу 3). Второй — стрелочная, у неё нет своего this, она наследует от места объявления — а объявлена она на уровне модуля/скрипта, где this = window (или undefined в модуле).

Отсюда правило: не используй стрелочные функции как методы объекта. Разбор ловушки — в следующей подглаве.

Когда лексический this спасает

Обратная ситуация — когда стрелочная функция решает проблему с потерянным контекстом. Классика — callback внутри метода:

class Counter {
  constructor() {
    this.count = 0;
  }
  start() {
    // стрелочная функция сохраняет this от start()
    setInterval(() => {
      this.count += 1;
      console.log(this.count);
    }, 1000);
  }
}

new Counter().start();   // считает: 1, 2, 3, ...

Здесь setInterval(() => ...) получает стрелочную функцию. У неё нет своего this, она наследует от start(). А в start() контекст — экземпляр Counter. Всё работает.

Сравни с версией на обычной функции:

class Counter {
  constructor() { this.count = 0; }
  start() {
    // ошибка: обычная функция в callback теряет this
    setInterval(function() {
      this.count += 1;    // TypeError: Cannot read properties of undefined
      console.log(this.count);
    }, 1000);
  }
}

Обычная функция внутри setInterval вызывается «отдельно» — setInterval не подставляет наш экземпляр как this. Классический breakage this (см. подглаву 2.3).

Другие особенности стрелочных функций

Помимо лексического this, стрелочные функции отличаются от обычных ещё несколькими штуками. Все они — тоже про «наследуется из внешней области, нет своего»:

  • Нет своего arguments. Псевдо-массив аргументов, доступный в обычных функциях, у стрелочной отсутствует. Если нужен список всех аргументов — используй rest-параметр (...args):
const sumAll = (...args) => {
  console.log(typeof arguments);    // => 'undefined'
  return args.reduce((acc, n) => acc + n, 0);
};

sumAll(1, 2, 3);                     // => 6
  • Нельзя вызвать с new. Стрелочная функция не является конструктором — попытка вызвать её через new даст TypeError.
  • .call(), .apply(), .bind() не меняют this. Явный контекст, переданный этими методами, игнорируется — стрелочная всё равно использует лексический this.
  • Не имеет prototype. У обычной функции есть свойство .prototype, у стрелочной — нет.

Когда использовать стрелочную, когда обычную

Правила простые:

  • Стрелочная — дефолт для callbacks и коротких utility-функций. Массив-методы (.map, .filter, .reduce), Promise-цепочки, setTimeout/setInterval, event listeners — везде стрелочные.
  • Обычная function или short-method — для методов класса или объекта. Там нужен свой this, который контекст-вызова подставит.
  • Never: стрелочная как метод объекта. { method: () => this.something } — ловушка, разобрана в следующей подглаве.

В подглавах разбираем: this в стрелочной функции подробно и опасность определения метода стрелочной функцией — самую частую ошибку с ней.