Стрелы времени: Почему стрелочные функции изменили JavaScript навсегда (и как не промахнуться)
Когда в 2015 году в JavaScript появились стрелочные функции, многие вздохнули с облегчением. Наконец-то можно писать короткие колбэки без function и return. Наконец-то this перестал быть проклятием. Наконец-то код стал красивее.
Но, как у любой суперсилы, у стрелочных функций есть тёмная сторона. Они не везде подходят. Они ведут себя иначе. И если не понять эти различия, стрелка может выстрелить мимо цели.
Сегодня мы повторим всё, что нужно знать о стрелочных функциях: от синтаксиса до подводных камней, от идеальных сценариев до тех мест, где они категорически не подходят.
Часть 1. Рождение стрелки: от многословия к лаконичности
Посмотрите, как эволюционировала одна и та же функция:
javascript
// Способ 1: классическое объявление
function square(x) {
return x * x;
}
// Способ 2: Function Expression
const square = function(x) {
return x * x;
};
// Способ 3: стрелочная (многострочная)
const square = (x) => {
return x * x;
};
// Способ 4: стрелочная (максимально короткая)
const square = x => x * x;
Что изменилось?
- Убрали слово function
- Добавили стрелку =>
- Если один параметр - скобки не нужны
- Если тело - одно выражение - return и {} не нужны
Часть 2. Синтаксис от А до Я
2.1 Без параметров
javascript
const getTime = () => Date.now();
const hello = () => console.log("Привет!");
2.2 С одним параметром (скобки необязательны)
javascript
const double = x => x * 2;
const square = x => { return x * x; }; // со скобками нужен return
2.3 С несколькими параметрами (скобки обязательны)
javascript
const add = (a, b) => a + b;
const multiply = (a, b, c) => a * b * c;
2.4 С телом из нескольких выражений (нужны {} и return)
javascript
const process = (a, b) => {
const sum = a + b;
const product = a * b;
return { sum, product };
};
2.5 Возврат объекта (ловушка!)
javascript
// ❌ Не работает ({} воспринимается как блок кода)
const getUser = () => { name: "Анна", age: 25 };
// ✅ Правильно (оборачиваем в скобки)
const getUser = () => ({ name: "Анна", age: 25 });
// ✅ Или с явным return
const getUser = () => {
return { name: "Анна", age: 25 };
};
Часть 3. Главная магия: this не теряется
Это самое важное отличие стрелочных функций. У них нет своего this. Они берут this из внешнего контекста (лексическое связывание).
3.1 Проблема обычных функций
javascript
const user = {
name: "Анна",
greet: function() {
setTimeout(function() {
console.log(`Привет, ${this.name}`); // undefined!
}, 1000);
}
};
user.greet(); // "Привет, undefined"
3.2 Решение со стрелкой
javascript
const user = {
name: "Анна",
greet: function() {
setTimeout(() => {
console.log(`Привет, ${this.name}`); // "Привет, Анна"
}, 1000);
}
};
user.greet(); // "Привет, Анна"
Что произошло? Стрелка не создала свой this, а взяла его из внешней функции greet, где this = user.
3.3 Стрелка как метод объекта - НЕ ДЕЛАЙТЕ ТАК
javascript
const obj = {
name: "Объект",
arrowMethod: () => {
console.log(this.name); // this = window (или undefined)
}
};
obj.arrowMethod(); // undefined
// Для методов объектов используйте обычные функции
const obj2 = {
name: "Объект",
regularMethod() {
console.log(this.name); // "Объект"
}
};
Часть 4. Нет arguments
У стрелочных функций нет собственного псевдомассива arguments.
javascript
// Обычная функция - есть
function regular() {
console.log(arguments); // [1, 2, 3]
}
regular(1, 2, 3);
// Стрелка - нет
const arrow = () => {
console.log(arguments); // ReferenceError: arguments is not defined
};
arrow(1, 2, 3);
Что делать? Используйте rest-параметры:
javascript
const sum = (...numbers) => numbers.reduce((a, b) => a + b, 0);
console.log(sum(1, 2, 3, 4)); // 10
Часть 5. Нельзя использовать как конструктор
Стрелочные функции нельзя вызывать с new. У них нет свойства prototype.
javascript
const Person = (name) => {
this.name = name;
};
// const user = new Person("Анна"); // TypeError: Person is not a constructor
// Только обычные функции
function RegularPerson(name) {
this.name = name;
}
const user = new RegularPerson("Анна"); // ✅
Часть 6. Нет prototype
У стрелочных функций нет свойства prototype. Они легче и проще.
javascript
function regular() {}
const arrow = () => {};
console.log(regular.prototype); // { constructor: regular }
console.log(arrow.prototype); // undefined
Часть 7. Когда использовать стрелочные функции (✅)
7.1 Короткие колбэки для массивов
javascript
const doubled = numbers.map(n => n * 2);
const adults = users.filter(user => user.age >= 18);
const total = orders.reduce((sum, order) => sum + order.price, 0);
7.2 Цепочки операций
javascript
const result = data
.filter(item => item.isActive)
.map(item => item.value)
.reduce((sum, val) => sum + val, 0);
7.3 Промисы и асинхронный код
javascript
fetch('/api/user')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
7.4 setTimeout / setInterval
javascript
setTimeout(() => {
console.log("Прошла секунда");
}, 1000);
7.5 Внутри методов классов (для сохранения this)
javascript
class Button {
constructor(label) {
this.label = label;
}
handleClick = () => {
console.log(`Нажата кнопка ${this.label}`);
}
}
7.6 Функции высшего порядка
javascript
const multiplyBy = factor => x => x * factor;
const double = multiplyBy(2);
const triple = multiplyBy(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Часть 8. Когда НЕ использовать стрелочные функции (❌)
8.1 Методы объектов (нужен свой this)
javascript
// Плохо
const counter = {
count: 0,
increment: () => {
this.count++; // this — не counter!
}
};
// Хорошо
const counter = {
count: 0,
increment() {
this.count++;
}
};
8.2 Конструкторы
javascript
// Плохо
const Widget = (width, height) => {
this.width = width; // не работает
};
// Хорошо
class Widget {
constructor(width, height) {
this.width = width;
this.height = height;
}
}
8.3 Функции, которым нужен свой arguments
javascript
// Плохо
const debug = (...args) => {
console.log(args); // работает, но не arguments
};
// Хорошо (если нужен именно arguments)
function debug() {
console.log(arguments);
}
8.4 Генераторы
javascript
// Стрелки не могут быть генераторами
function* generator() {
yield 1;
yield 2;
}
8.5 Методы, которые используют super
javascript
class Parent {
method() {
console.log("Parent");
}
}
class Child extends Parent {
// ❌ Стрелка не работает с super
arrowMethod = () => {
super.method(); // Ошибка!
}
// ✅ Обычный метод работает
regularMethod() {
super.method(); // "Parent"
}
}
Часть 9. Стрелки и деструктуризация - идеальная пара
javascript
const users = [
{ name: "Анна", age: 25 },
{ name: "Борис", age: 30 }
];
// Красиво и читаемо
const names = users.map(({ name }) => name);
const adults = users.filter(({ age }) => age >= 18);
const totalAge = users.reduce((sum, { age }) => sum + age, 0);
Часть 10. Сравнение: стрелка vs обычная функция
Часть 11. Реальные паттерны
11.1 Композиция функций
javascript
const compose = (f, g) => x => f(g(x));
const add1 = x => x + 1;
const multiply2 = x => x * 2;
const add1ThenMultiply2 = compose(multiply2, add1);
console.log(add1ThenMultiply2(5)); // (5 + 1) * 2 = 12
11.2 Каррирование
javascript
const multiply = a => b => a * b;
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
11.3 Условный рендеринг в React
jsx
const UserGreeting = ({ user, isLoggedIn }) => (
<div>
{isLoggedIn ? (
<h1>С возвращением, {user.name}!</h1>
) : (
<button onClick={() => login()}>Войти</button>
)}
</div>
);
11.4 Обработка событий с сохранением контекста
jsx
class SearchForm extends React.Component {
state = { query: "" };
handleChange = (e) => {
this.setState({ query: e.target.value }); // this = экземпляр
};
render() {
return <input onChange={this.handleChange} />;
}
}
Часть 12. Подводные камни
Камень #1: Возврат объекта без скобок
javascript
// ❌
const getUser = () => { name: "Анна" };
console.log(getUser()); // undefined
// ✅
const getUser = () => ({ name: "Анна" });
Камень #2: this в глобальной стрелке
javascript
// В браузере
const arrow = () => console.log(this);
arrow(); // window (не undefined!)
// В строгом режиме тоже window (обычная функция дала бы undefined)
Камень #3: Нечитаемая цепочка стрелок
javascript
// Плохо (слишком много стрелок подряд)
const result = data.map(x => x.items).filter(x => x.active).map(x => x.price).reduce((a, b) => a + b, 0);
// Хорошо (разбито на строки)
const result = data
.map(x => x.items)
.filter(x => x.active)
.map(x => x.price)
.reduce((sum, price) => sum + price, 0);
Камень #4: Слишком умная однострочка
javascript
// Плохо (непонятно)
const validate = user => user && user.age >= 18 && user.isActive || user.isAdmin;
// Хорошо (понятно)
const validate = user => {
if (!user) return false;
if (user.isAdmin) return true;
return user.age >= 18 && user.isActive;
};
Часть 13. Производительность (мифы и факты)
Миф: Стрелочные функции медленнее обычных.
Факт: Разница настолько мала, что вы её никогда не заметите. Современные движки оптимизируют стрелки отлично.
javascript
// Измерять бессмысленно - разница в наносекундах
console.time("regular");
for (let i = 0; i < 10000000; i++) {
(function(x) { return x * 2; })(i);
}
console.timeEnd("regular");
console.time("arrow");
for (let i = 0; i < 10000000; i++) {
(x => x * 2)(i);
}
console.timeEnd("arrow");
Выбирайте по читаемости, а не по скорости.
Итог: Манифест стрелочных функций
- Короткий синтаксис - пишите меньше, делайте больше.
- Нет своего this - берут из внешнего контекста (суперсила для колбэков).
- Не использовать как методы объектов - для методов нужны обычные функции.
- Не использовать как конструкторы - для классов есть class.
- Нет arguments - используйте ...rest.
- Идеальны для колбэков - map, filter, reduce, setTimeout, промисы.
- Читаемость важнее краткости - не жертвуйте понятностью ради одной строки.
Финальный тест (проверьте себя):
javascript
const obj = {
name: "JS",
getName: () => this.name
};
console.log(obj.getName());
const calculator = {
value: 10,
double: () => this.value * 2,
triple() { return this.value * 3; }
};
console.log(calculator.double());
console.log(calculator.triple());
const add = x => y => x + y;
const add5 = add(5);
console.log(add5(3));
Ответы: undefined (стрелка берёт глобальный this), NaN (this.value - undefined → NaN), 30, 8.
Стрелочные функции - это не просто синтаксический сахар. Это новый способ думать о функциях в JavaScript. Они делают код чище, избавляют от головной боли с this и открывают двери в мир функционального программирования. Но, как любой инструмент, они требуют понимания, когда их использовать, а когда лучше выбрать обычную функцию. Используйте их мудро, и ваш код станет красивее, понятнее и современнее. Стреляйте в цель!