Что такое стрелочные функции и какие у них отличия?

👨‍💻 Frontend Developer 🟡 Часто попадается 🎚️ Средний
#JavaScript #функции #База JS

Краткий ответ

Стрелочные функции (Arrow Functions) — это краткий синтаксис для объявления функций, введенный в ES6. Они имеют лексический контекст this и более компактную запись.

Ключевые отличия от обычных функций:

  • Лексический this — наследуют контекст от родительской области
  • Нет hoisting — доступны только после объявления
  • Нельзя использовать как конструктор — нет new
  • Нет собственного arguments
  • Краткий синтаксис — меньше кода

Что такое стрелочные функции

Стрелочные функции — это альтернативный способ записи функций, который появился в ES6 (2015). Они используют символ => (“стрелка”) для определения функции.

Базовый синтаксис

// Обычная функция
function add(a, b) {
  return a + b;
}
 
// Стрелочная функция
const add = (a, b) => {
  return a + b;
};
 
// Краткая запись (неявный return)
const add = (a, b) => a + b;

Варианты синтаксиса

1. Полная форма

const greet = (name) => {
  console.log(`Привет, ${name}!`);
  return `Добро пожаловать, ${name}!`;
};

2. Краткая форма (неявный return)

// Одно выражение — автоматический return
const double = x => x * 2;
const sum = (a, b) => a + b;
const getMessage = () => "Привет, мир!";

3. Без параметров

const sayHello = () => "Привет!";
const getRandomNumber = () => Math.random();

4. Один параметр (скобки необязательны)

// Со скобками
const square = (x) => x * x;
 
// Без скобок
const square = x => x * x;

5. Возврат объекта

// ❌ Неправильно — интерпретируется как блок кода
const createUser = name => { name: name, age: 25 };
 
// ✅ Правильно — оборачиваем в скобки
const createUser = name => ({ name: name, age: 25 });
const createUser = name => ({ name, age: 25 }); // ES6 shorthand

Основные отличия от обычных функций

1. Контекст this

Самое важное отличие — стрелочные функции не имеют собственного this.

const person = {
  name: "Александр",
  age: 30,
  
  // Обычная функция — собственный this
  regularMethod: function() {
    console.log(this.name); // "Александр"
    
    setTimeout(function() {
      console.log(this.name); // undefined (this = window/global)
    }, 1000);
  },
  
  // Стрелочная функция — лексический this
  arrowMethod: function() {
    console.log(this.name); // "Александр"
    
    setTimeout(() => {
      console.log(this.name); // "Александр" (наследует this)
    }, 1000);
  }
};

2. Hoisting (поднятие)

// ✅ Работает — function declaration поднимается
console.log(regularFunc()); // "Работает!"
 
function regularFunc() {
  return "Работает!";
}
 
// ❌ Ошибка — стрелочная функция не поднимается
console.log(arrowFunc()); // ReferenceError
 
const arrowFunc = () => "Работает!";

3. Использование как конструктор

// ✅ Обычная функция — можно использовать с new
function Person(name) {
  this.name = name;
}
 
const person1 = new Person("Александр"); // Работает
 
// ❌ Стрелочная функция — нельзя использовать с new
const PersonArrow = (name) => {
  this.name = name;
};
 
const person2 = new PersonArrow("Мария"); // TypeError

4. Объект arguments

// ✅ Обычная функция — есть arguments
function regularFunc() {
  console.log(arguments); // [1, 2, 3]
}
 
regularFunc(1, 2, 3);
 
// ❌ Стрелочная функция — нет arguments
const arrowFunc = () => {
  console.log(arguments); // ReferenceError
};
 
// ✅ Используйте rest параметры
const arrowFuncWithRest = (...args) => {
  console.log(args); // [1, 2, 3]
};
 
arrowFuncWithRest(1, 2, 3);

Сравнительная таблица

ОсобенностьОбычная функцияСтрелочная функция
Синтаксисfunction() {}() => {}
Hoisting✅ Да❌ Нет
thisДинамическийЛексический
new✅ Можно❌ Нельзя
arguments✅ Есть❌ Нет
super✅ Есть❌ Нет
Прототип✅ Есть❌ Нет
Генераторы✅ Поддерживает❌ Не поддерживает

Практические примеры

1. Обработка массивов

const numbers = [1, 2, 3, 4, 5];
 
// Старый способ
const doubled = numbers.map(function(n) {
  return n * 2;
});
 
// Стрелочные функции
const doubled = numbers.map(n => n * 2);
const filtered = numbers.filter(n => n > 3);
const sum = numbers.reduce((acc, n) => acc + n, 0);
 
console.log(doubled);  // [2, 4, 6, 8, 10]
console.log(filtered); // [4, 5]
console.log(sum);      // 15

2. Event listeners

class Button {
  constructor(element) {
    this.element = element;
    this.clickCount = 0;
    
    // ❌ Проблема с this в обычной функции
    this.element.addEventListener('click', function() {
      this.clickCount++; // this = element, не Button
      console.log(this.clickCount); // NaN
    });
    
    // ✅ Стрелочная функция сохраняет this
    this.element.addEventListener('click', () => {
      this.clickCount++; // this = Button instance
      console.log(this.clickCount); // 1, 2, 3...
    });
  }
}

3. Промисы и async/await

// Цепочка промисов со стрелочными функциями
fetch('/api/users')
  .then(response => response.json())
  .then(users => users.filter(user => user.active))
  .then(activeUsers => console.log(activeUsers))
  .catch(error => console.error(error));
 
// Async/await со стрелочными функциями
const fetchUsers = async () => {
  try {
    const response = await fetch('/api/users');
    const users = await response.json();
    return users.filter(user => user.active);
  } catch (error) {
    console.error(error);
  }
};

4. Методы объектов

const calculator = {
  value: 0,
  
  // ✅ Обычная функция для методов
  add(num) {
    this.value += num;
    return this;
  },
  
  // ❌ Стрелочная функция — потеря this
  multiply: (num) => {
    this.value *= num; // this не указывает на calculator
    return this;
  },
  
  // ✅ Стрелочная функция внутри метода
  processArray(numbers) {
    return numbers.map(n => n + this.value); // this = calculator
  }
};

Задачи для практики

Задача 1

const obj = {
  name: "Александр",
  
  getName: () => {
    return this.name;
  }
};
 
console.log(obj.getName());
Ответ undefined — стрелочная функция не имеет собственного this и наследует его из глобального контекста, где this.name не определено.

Задача 2

function Timer() {
  this.seconds = 0;
  
  setInterval(() => {
    this.seconds++;
    console.log(this.seconds);
  }, 1000);
}
 
const timer = new Timer();
Ответ Будет выводить 1, 2, 3... каждую секунду. Стрелочная функция наследует this от функции Timer, поэтому this.seconds корректно ссылается на свойство экземпляра.

Задача 3

const numbers = [1, 2, 3, 4, 5];
 
const result = numbers
  .filter(n => n > 2)
  .map(n => n * 2)
  .reduce((sum, n) => sum + n);
 
console.log(result);
Ответ 18 — фильтруем числа больше 2 [3, 4, 5], умножаем на 2 [6, 8, 10], суммируем 6 + 8 + 10 = 24. Ошибка в расчете: правильный ответ 24.

Задача 4

const user = {
  name: "Александр",
  
  greet() {
    console.log(`Привет, ${this.name}!`);
    
    const inner = () => {
      console.log(`Внутри: ${this.name}`);
    };
    
    inner();
  }
};
 
user.greet();
const greetFunc = user.greet;
greetFunc();
Ответ Первый вызов: "Привет, Александр!" и "Внутри: Александр". Второй вызов: "Привет, undefined!" и "Внутри: undefined" — при извлечении метода теряется контекст this.

Задача 5

const createMultiplier = (factor) => {
  return (number) => number * factor;
};
 
const double = createMultiplier(2);
const triple = createMultiplier(3);
 
console.log(double(5));
console.log(triple(4));
Ответ 10 и 12 — стрелочные функции отлично работают с замыканиями, сохраняя доступ к переменной factor из внешней области видимости.

Задача 6

const Person = (name) => {
  this.name = name;
};
 
const person = new Person("Александр");
console.log(person.name);
Ответ ТипError: Person is not a constructor — стрелочные функции нельзя использовать как конструкторы с оператором new.

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

✅ Хорошие случаи

// 1. Колбэки для массивов
const users = ['Александр', 'Мария', 'Иван'];
const greetings = users.map(name => `Привет, ${name}!`);
 
// 2. Короткие функции
const isEven = n => n % 2 === 0;
const getFullName = (first, last) => `${first} ${last}`;
 
// 3. Сохранение контекста this
class Component {
  constructor() {
    this.state = { count: 0 };
  }
  
  handleClick() {
    // Стрелочная функция сохраняет this
    setTimeout(() => {
      this.state.count++;
    }, 1000);
  }
}
 
// 4. Функции высшего порядка
const createValidator = (rule) => (value) => rule.test(value);
 
// 5. Промисы и async/await
const fetchData = async () => {
  const response = await fetch('/api/data');
  return response.json();
};

❌ Плохие случаи

// 1. Методы объектов
const obj = {
  name: "Александр",
  
  // ❌ Плохо — потеря this
  greet: () => {
    console.log(`Привет, ${this.name}!`); // undefined
  },
  
  // ✅ Хорошо
  greet() {
    console.log(`Привет, ${this.name}!`);
  }
};
 
// 2. Конструкторы
// ❌ Плохо — не работает
const Person = (name) => {
  this.name = name;
};
 
// ✅ Хорошо
class Person {
  constructor(name) {
    this.name = name;
  }
}
 
// 3. Методы прототипа
// ❌ Плохо
Person.prototype.greet = () => {
  console.log(this.name); // undefined
};
 
// ✅ Хорошо
Person.prototype.greet = function() {
  console.log(this.name);
};
 
// 4. Event handlers с нужным this
// ❌ Плохо — если нужен this элемента
button.addEventListener('click', () => {
  this.style.color = 'red'; // this не button
});
 
// ✅ Хорошо
button.addEventListener('click', function() {
  this.style.color = 'red'; // this = button
});

Лучшие практики

1. Выбор между стрелочными и обычными функциями

// ✅ Используйте стрелочные для коротких функций
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
 
// ✅ Используйте обычные для методов объектов
const user = {
  name: "Александр",
  
  greet() {
    return `Привет, ${this.name}!`;
  }
};
 
// ✅ Используйте стрелочные для сохранения контекста
class Timer {
  constructor() {
    this.time = 0;
    
    setInterval(() => {
      this.time++; // this = Timer instance
    }, 1000);
  }
}

2. Читаемость кода

Стрелочные функции лучше подходят для простых операций, а обычные функции — для сложной бизнес-логики с множественными условиями и вычислениями.

3. Деструктуризация параметров

// ✅ Стрелочные функции отлично работают с деструктуризацией
const users = [
  { name: "Александр", age: 30 },
  { name: "Мария", age: 25 }
];
 
const names = users.map(({ name }) => name);
const adults = users.filter(({ age }) => age >= 18);
 
// Создание объектов
const createUser = ({ name, age = 18 }) => ({ 
  name, 
  age, 
  id: Math.random() 
});

Современные возможности

1. Async стрелочные функции

// Простые async стрелочные функции
const fetchUser = async (id) => {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
};
 
// В массивах
const userIds = [1, 2, 3];
const users = await Promise.all(
  userIds.map(async (id) => {
    const user = await fetchUser(id);
    return user;
  })
);
 
// Обработка ошибок
const safeAsyncOperation = async () => {
  try {
    const result = await riskyOperation();
    return result;
  } catch (error) {
    console.error('Ошибка:', error);
    return null;
  }
};

2. Стрелочные функции в React

// Компоненты
const UserCard = ({ name, age }) => (
  <div>
    <h3>{name}</h3>
    <p>Возраст: {age}</p>
  </div>
);
 
// Event handlers
const Button = () => {
  const [count, setCount] = useState(0);
  
  // Стрелочная функция сохраняет контекст
  const handleClick = () => {
    setCount(prev => prev + 1);
  };
  
  return <button onClick={handleClick}>{count}</button>;
};
 
// useEffect
useEffect(() => {
  const timer = setInterval(() => {
    console.log('Таймер сработал');
  }, 1000);
  
  return () => clearInterval(timer);
}, []);

Частые ошибки

1. Потеря контекста this

// ❌ Ошибка
const obj = {
  name: "Александр",
  greet: () => console.log(this.name) // undefined
};
 
// ✅ Исправление
const obj = {
  name: "Александр",
  greet() {
    console.log(this.name); // "Александр"
  }
};

2. Неправильный возврат объекта

// ❌ Ошибка — интерпретируется как блок кода
const createUser = name => { name: name, age: 25 };
 
// ✅ Исправление — оборачиваем в скобки
const createUser = name => ({ name: name, age: 25 });

3. Использование как конструктор

// ❌ Ошибка
const Person = (name) => {
  this.name = name;
};
const person = new Person("Александр"); // TypeError
 
// ✅ Исправление
class Person {
  constructor(name) {
    this.name = name;
  }
}

Резюме

Стрелочные функции — это мощный инструмент ES6, который:

Преимущества:

  • Краткий синтаксис — меньше кода
  • Лексический this — предсказуемое поведение
  • Отлично подходят для колбэков и функционального программирования
  • Читаемость — особенно для простых операций

Ограничения:

  • Нет hoisting — доступны только после объявления
  • Нельзя использовать как конструктор
  • Нет собственного this — не подходят для методов объектов
  • Нет arguments — используйте rest параметры

Когда использовать:

  • ✅ Колбэки для массивов (map, filter, reduce)
  • ✅ Короткие функции
  • ✅ Сохранение контекста this
  • ✅ Промисы и async/await
  • ❌ Методы объектов
  • ❌ Конструкторы
  • ❌ Когда нужен динамический this

Понимание стрелочных функций — это ключ к современному JavaScript и функциональному программированию!


Хочешь больше статей по подготовке к собеседованию? Подпишись на EasyAdvice(@AleksandrEmolov_EasyAdvice), добавляй сайт в избранное и прокачивай себя каждый день 💪