main

5 отличий между обычными и стрелочными функциями

Наши соц. сети: instagram, fb, tg

В JS ты можешь инициализировать функцию несколькими способами.

Обычно - это вариант с использованием ключевого слова function

// Function declaration (объявление функции)

function greet(who) {

return `Hello, ${who}!`;

}



// Function expression (функциональное выражение)

const greet = function(who) {

return `Hello, ${who}`;

}


Объявление функции и функциональное выражения я буду именовать как обыкновенная функция.

Второй путь, стал доступен в ES2015 - это стрелочный синтаксис:


const greet = (who) => {

return `Hello, ${who}!`;

}


Итак, встает хороший вопрос, если у нас есть два варианта создания функции, то какой когда лучше выбрать? 🤔

Далее в статье я расскажу про различия и по окончанию ты поймешь, что лучше подходит под твои нужды.

1. this

1.1 Обыкновенные функции

Внутри обыкнвоенной функции значение this динамическое (в зависимости от контекста исполнения).
Динамический контекст означает, что значение this зависит от того как была вызвана функция. В JS существуте 4е способа как ты можешь вызвать функцию.
Во время обычного выполнения значение this эквивалентно глобальному объекту:

function myFunction() {

console.log(this);

}

// Простое выполнение

myFunction(); // контекстом будет (window)


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

const myObject = {

method() {

console.log(this);

}

};

// Вызов функции объекта

myObject.method(); // контекстом будет myObject


Косвенный вызов используя myFunc.call(thisVal, arg1, ..., argN) или myFunc.apply(thisVal, [arg1, ..., argN]), значение this эквивалентно первому аргументу:

function myFunction() {

console.log(this);

}

const myContext = { value: 'A' };

myFunction.call(myContext); // { value: 'A' }

myFunction.apply(myContext); // { value: 'A' }


Вызов с помощью конструктора используя ключевое слово new, значение this эквивалентно новосозданной сущности:

function MyFunction() {

console.log(this);

}

new MyFunction(); // MyFunction


1.2 Стрелочные функции

Поведение this внутри стрелочной функции отличается от поведения this внутри обычной функции.
Не имеет значения как она была вызвана, значение this внутри стрелочной функции всегда эквивалентно значения this внешней функции. Другими словами функция не создает собственный контекст исполнения, она использует внешний.
В примере выше, myMethod() это внешняя функциия для стрелочной функции callback():

const myObject = {

myMethod(items) {

console.log(this); // myObject

const callback = () => {

console.log(this); // myObject

};

items.forEach(callback);

}

};

myObject.myMethod([1, 2, 3]);


значение this внутри стрелочной функции callback() эквивалентно значению this внешней функции myMethod().
Это одна из самых крутых фишек стрелочных функций. Когда ты используешь колбек внутри метода, ты можешь быть уверен, что стрелочная функция не создаст собственный this: больше не нужны обходные пути типа const self = this или callback.bind(this).
Даже если ты попытаешься вызвать стрелочную функцию через myArrowFunc.call(this) или myArrowFunc.apply(this), то ничего не изменится, у неё все так же будет this = внешнему this.

2. Конструкторы

2.1 Обыкновенные функции

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

Например, Car() функция создаст объект автомобиля:

function Car(color) {

this.color = color;

}

const redCar = new Car('red');

redCar instanceof Car; // => true


Car - это обыкновенная функция и когда мы её вызывем с помощью ключевого слова new, она создает новый объект типа Car.

2.2 Стрелочные функции

Как следствие того, что стрелочные функции не имеют собственного this они не могут быть использованы для создания объектов.
Если ты попытаешься вызвать стрелочную функцию с использованием ключевого слова new, JS кинет исключение:

const Car = (color) => {

this.color = color;

};

const redCar = new Car('red'); // TypeError: Car is not a constructor


Вызов new Car('red'), где Car это стрелочная функция, будет сгенерирована ошибка TypeError: Car is not a constructor.

3. Объект arguments

3.1 Обыкновенные функции

Внутри тела обыкновенной функции, существует специальный массив arguments содержащий список аргументов с которым функция была вызвана.
Давай вызовем функцию myFunction с двумя аргументами:

function myFunction() {

console.log(arguments);

}

myFunction('a', 'b'); // { 0: 'a', 1: 'b'}


массив arguments будет содержать аргументы: 'a' и 'b'.

3.2 Стрелочные функции

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

Давай попробуем:


function myRegularFunction() {

const myArrowFunction = () => {

console.log(arguments);

}

myArrowFunction('c', 'd');

}

myRegularFunction('a', 'b'); // { 0: 'a', 1: 'b' }


Стрелочаня функция myArrowFunction() вызвается с аргументами 'c', 'd'. Но до сих пор, внутри тела функции, arguments такой же точно, как в функции myRegularFunction().

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


function myRegularFunction() {

const myArrowFunction = (...args) => {

console.log(args);

}

myArrowFunction('c', 'd');

}

myRegularFunction('a', 'b'); // { 0: 'c', 1: 'd' }


Параметр ...args собирает все аргументы преданные при вызове стрелочной функции: { 0: 'c', 1: 'd' }.

4. Неявный return

4.1 Обыкновенные функции

Только использование выражения return возвращает результат выполнения функции:

function myFunction() {

return 42;

}

myFunction(); // => 42


Если return отсутствует внутри стрелочной функции, или после return нет выражения, функция вернет undefined:

function myEmptyFunction() {

42;

}

function myEmptyFunction2() {

42;

return;

}

myEmptyFunction(); // => undefined

myEmptyFunction2(); // => undefined


4.2 Стрелочные функции

Ты можешь вернуть значение из стрелочной функции, точно таким же способом, как и из обычной функции, но с одним полезным исключением.

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


const increment = (num) => num + 1;

increment(41); // => 42


Функция increment() содержит только одну инструкцию: num + 1. Это выражения неявно возвращается стрелочной функцией без использования ключевого слова return.

5. Методы

5.1 Обыкновенные функции

Чаще всего, обыкновенная функция используется для создания методов класса.

В классе Hero ниже, метод logName() создан с использованием синтаксиса обычной функции:

class Hero {

constructor(heroName) {

this.heroName = heroName;

}

logName() {

console.log(this.heroName);

}

}

const batman = new Hero('Batman');


Иногда тебе будет нужно применить метод в качестве колбека, например для setTimeout() или для event listener`а. В таких случаях, ты можешь столкнуться с проблемой при поптыке получить доступ к this.
Например, давай попробуем использовать logName() метод как колбек для setTimeout():

setTimeout(batman.logName, 1000);

// after 1 second logs "undefined"


По истечению 1 секунды ты увидишь в консоли undefined. setTimeout() выполняет обычный вызов функции logName(где this это глобальный объект). В данном случае метод отделен от объекта.

Давай попробуем вручную привязать контекст:


setTimeout(batman.logName.bind(batman), 1000);

// after 1 second logs "Batman"


batman.logName.bind(batman) привязывает this к оъекту batman. Теперь ты можешь убедиться, что контекст не потерян.
Привязка this вручную необходимое зло😈 Но есть выход, ты можешь использовать стрелочные функции.

5.2 Стрелочные функции

Ты можешь использовать стрелочные функции как методы, внутри класса.

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

Давай попробуем:


class Hero {

constructor(heroName) {

this.heroName = heroName;

}

logName = () => {

console.log(this.heroName);

}

}

const batman = new Hero('Batman');


Сейчас ты можешь использовать batman.logName без какой-либо привязки this. Значение this внутри метода logName() всегда объект класса:

setTimeout(batman.logName, 1000);

// after 1 second logs "Batman"


6. Выводы

Понимание разница между обыкновенными и стрелочными функциями поможет сделать правильный выбор, что тебе сейчас лучше исопльзовать.

Значение this внутри обыкновенной функции динамически зависит от контекста вызова. Собственный this внутри стрелочной функции отсутствует и она ссылается на this внешней функции.
Массив arguments внутри обыкновенной функции содержит список аргументов функции. Стрелочная функция, не имеет массива arguments (но ты можешь использовать деструктуризацию, для иммитации аналога ...args).
Если в стрелочной функции содержится одна иснтрукция, то ты можешь использовать неявный return, даже без использованяи ключевого слова return.
Последнее в списке, но не по важности - ты можешь использовать синтаксис стрелочных функций для внутри класса. При этом в качестве this будет выступать объект класса.

Уверен, что тебе было полезно, читай ещё больше статей в нашем блоге💪