В чём разница между классической функцией и стрелочной

В JavaScript существуют два основных способа объявления функций: классическая (обычная) функция и стрелочная функция (arrow function). Оба синтаксиса позволяют создавать функции, но имеют различия в поведении, синтаксисе, работе с контекстом (this), аргументами и объектами, а также в возможности использования как конструкторов. Эти различия критически важны для правильного понимания и использования функций в различных ситуациях.

1. Синтаксис

Классическая функция:

function add(a, b) {
return a + b;
}

Или функциональное выражение:

const add = function(a, b) {
return a + b;
};

Стрелочная функция:

const add = (a, b) => a + b;

Стрелочные функции компактнее и особенно удобны при использовании в колбэках, функциональных операциях (map, filter, reduce) и в React-компонентах.

2. Поведение this

Классическая функция:

  • this зависит от контекста вызова.

  • Может быть переопределено через call, apply, bind.

const user = {
name: "Аня",
greet: function() {
console.log(\`Привет, ${this.name}\`);
}
};
user.greet(); // Привет, Аня

Стрелочная функция:

  • Не имеет собственного this.

  • Наследует this из внешнего лексического контекста.

const user = {
name: "Аня",
greet: () => {
console.log(\`Привет, ${this.name}\`);
}
};
user.greet(); // Привет, undefined (или ошибка)

Внутри стрелочной функции this не ссылается на объект user, а на this из места, где функция была определена (например, глобальный объект).

3. Поведение с bind, call, apply

Классическая функция:

  • Можно явно привязать this.
function show() {
console.log(this.value);
}
const obj = { value: 42 };
show.call(obj); // 42

Стрелочная функция:

  • Невозможно переопределить this, даже через bind.
const show = () => console.log(this.value);
const obj = { value: 42 };
show.call(obj); // undefined

Стрелочная функция захватывает this один раз из окружающей области и не поддаётся переназначению.

4. Использование в методах объектов

Стрелочные функции не подходят в роли методов объекта, где требуется this, указывающий на сам объект.

const counter = {
count: 0,
inc: function() {
this.count++;
}
};
counter.inc(); // count = 1

При использовании стрелочной функции:

const counter = {
count: 0,
inc: () => {
this.count++;
}
};
counter.inc(); // this = глобальный объект  undefined или ошибка

5. Возможность быть конструктором (new)

Классическая функция:

  • Может использоваться как конструктор с new.
function Person(name) {
this.name = name;
}
const p = new Person("Антон");
console.log(p.name); // Антон

Стрелочная функция:

  • Не может быть вызвана с new.

  • Попытка вызвать вызовет ошибку.

const Person = (name) => {
this.name = name;
};
const p = new Person("Антон"); // TypeError: Person is not a constructor

Это связано с тем, что у стрелочной функции нет прототипа и собственного this.

6. Аргументы (arguments)

Классическая функция:

  • Имеет встроенный объект arguments, содержащий все переданные аргументы.
function test() {
console.log(arguments);
}
test(1, 2, 3); // \[1, 2, 3\]

Стрелочная функция:

  • Не имеет собственного arguments.

  • Можно получить arguments только из внешней (родительской) функции.

const test = () => {
console.log(arguments);
};
test(1, 2); // ReferenceError: arguments is not defined

Если стрелочная функция вложена в обычную, она может использовать arguments из неё:

function outer() {
const inner = () => {
console.log(arguments);
};
inner(1, 2, 3); // покажет аргументы outer()
}
outer(5, 6); // \[5, 6\]

7. Поддержка super и new.target

Классическая функция:

  • Поддерживает super в методах классов.

  • Поддерживает new.target — указывает, была ли функция вызвана через new.

function Foo() {
console.log(new.target);
}
new Foo(); // \[Function: Foo\]

Стрелочная функция:

  • Не имеет super и new.target.
const Foo = () => {
console.log(new.target);
};
new Foo(); // TypeError

8. Поведение в таймерах и callback-функциях

Классическая функция:

function Timer() {
this.seconds = 0;
setInterval(function () {
this.seconds++;
console.log(this.seconds); // NaN или ошибка
}, 1000);
}

this в функции внутри setInterval указывает на глобальный объект.

Решения:

  • использовать bind(this)

  • сохранить this в замыкании (const self = this)

  • использовать стрелочную функцию

Стрелочная функция:

function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++;
console.log(this.seconds);
}, 1000);
}

В этом случае this внутри стрелочной функции указывает на экземпляр Timer.

9. Сокращённая запись return

Стрелочная функция:

Если тело функции состоит из одного выражения, можно опустить фигурные скобки и ключевое слово return:

const double = x => x \* 2;

Классическая функция:

Такой возможности нет — нужно писать явно:

function double(x) {
return x \* 2;
}

10. Использование в функциональном стиле

Стрелочные функции активно применяются в методах массивов и функциональных выражениях:

const arr = \[1, 2, 3\];
const squared = arr.map(x => x \*\* 2); // \[1, 4, 9\]

Это делает код короче и читабельнее по сравнению с обычными функциями:

const squared = arr.map(function(x) {
return x \*\* 2;
});

11. Объект как возвращаемое значение

При возвращении объекта из стрелочной функции без фигурных скобок нужно использовать круглые скобки:

const getUser = () => ({ name: "Лена", age: 25 });

Если написать:

const getUser = () => { name: "Лена" };

То функция вернёт undefined, потому что фигурные скобки интерпретируются как блок, а не как объект.

12. Переопределение this внутри метода класса

Классические методы:

class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(\`Привет, ${this.name}\`);
}
}

Если метод используется как callback, контекст может потеряться:

const u = new User("Игорь");
setTimeout(u.greet, 1000); // Привет, undefined
Решение  использовать стрелочную функцию:
class User {
constructor(name) {
this.name = name;
this.greet = () => {
console.log(\`Привет, ${this.name}\`);
};
}
}

Теперь метод greet всегда будет вызываться с правильным this.

13. Перечисляемость (enumerability)

Стрелочные функции — всегда неперечисляемые свойства, если объявлены в объекте:

const obj = {
arrow: () => {},
regular: function () {}
};
console.log(Object.keys(obj)); // \["arrow", "regular"\]

Обе функции перечисляемы, но их поведение при переопределении прототипов различается в классах и конструкциях.