Стрелочная функция (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 в стрелочной функции подробно и опасность определения метода стрелочной функцией — самую частую ошибку с ней.