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

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

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

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

// Function declaration (объявление функции) function greet(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 такой же точно, как в функцииmy RegularFunction().

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

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 будет выступать объект класса.

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

0
8 комментариев
Написать комментарий...
Oleg Garvin

arguments устарело 5 лет назад, не учите неокрепшие умы плохим практикам.

Ответить
Развернуть ветку
Anton Smets

Тем более это и не массив вовсе 😂

Ответить
Развернуть ветку
Войти в IT
Автор

Все верно вы говорите, arguments - устарело, но в данной статье мы разбирали ОТЛИЧИЯ стрелочных и обычных функций, сделаю пометку в статье, что arguments лучше не использовать.

Ответить
Развернуть ветку
Влад Бабаев

По поводу последнего пункта. Не совсем понял, если this стрелочной функции берется из контекста, в котором она определена, то почему this == batman? По идее this должен быть равен Hero.prototype? Ведь именно там лежит метод. 

Ответить
Развернуть ветку
Влад Бабаев

Тэк, ну я полез проверять и понял, что метод, определенный через стрелочную функцию, по сути является свойством экземпляра и хранится на объекте batman. Вероятно поэтому this и не теряется. Это стои ло бы объяснить в материале( 

Ответить
Развернуть ветку
Крылов Глеб

В пункте 4.1 ошибка -в фразе Если return отсутствует внутри стрелочной функции, должна быть не стрелочная о обычная функция

Ответить
Развернуть ветку
Алексей Атаманов

Примеры кода без отступов выглядят просто ужасно 

Ответить
Развернуть ветку
Александр Грачев

Спасибо, очень подробно и понятно!

Ответить
Развернуть ветку
5 комментариев
Раскрывать всегда