Особенности стрелочных функций
ES2015 принёс нам новый способ работы с функциями — стрелочные функции. И хотя уже прошло практически 5 лет, многие до сих пор до конца не понимают их особенностей и чем они отличаются от обычных функций, когда их можно или даже нужно использовать, а когда лучше от них отказаться. Вот об этом и поговорим.
Для начала давайте определимся с основными характеристиками, в которых стрелочные функции имеют особенности и отличия от традиционных функций:
- Контекст (он же
this
) - Функция-конструктор
- Аргументы
- Возвращаемые значения
- Использование в качестве метода
И давайте пойдём по порядку.
Контекст (он же this
)
Конечно же, самые главные особенности стрелочных функций касаются контекста. И здесь нам нужно вспомнить что такое контекст и как он вобще у функции появляется. У традиционных функций в javascript контекст динамический. Т.е. он зависит от того, как была вызвана функция. А как она может быть вызвана? В javascript есть аж 4 способа вызвать функцию.
Глобальный вызов — контекстом будет глобальный объект window
(в strict mode будет undefined
)
function myFunc() { console.log(this);}myFunc(); // => глобальный объект window (или undefined, если указан strict mode)
Вызов метода — контекстом будет объект, вызывающий функцию
const myObject = { method() { console.log(this); }};myObject.method(); // => myObject
Вызов с помощью apply
и call
— контекстом будет первый переданный в функцию аргумент
function myFunc() { console.log(this);}const newContext = { value: 'A' };myFunc.call(newContext); // => { value: 'A' }myFunc.apply(newContext); // => { value: 'A' }
Конструктор — контекстом будет новый только что созданный объект
function MyFunc() { console.log(this);}new MyFunc(); // => экземпляр MyFunction
Так вот для стрелочных функций всё это не работает. Независимо от того, как стрелочная функция вызвана, её контекстом всегда будет контекст внешней функции. Другими словами, у стрелочной функции нет собственного контекста.
const myObject = { myMethod(items) { console.log(this); // => myObject const callback = () => { console.log(this); // => myObject }; items.forEach(callback); }};myObject.myMethod([1, 2, 3]); // => myMethod
В примере выше контекстом стрелочной функции callback
будет являться контекст функции myMethod
. Это главная особенность стрелочных функций. И это же и их главное преимущество. Даже если вы попробуете вызвать стрелочную функцию с помощью call
или apply
это не сработает и контекст функции не поменяется.
Функция-конструктор
Традиционные функции можно использовать в качестве конструктора.
function User(name) { this.name = name;}const admin = new User('John Doe');admin instanceof User; // => true
Со стрелочными функциями такой номер не пройдёт. Попытка использовать её как конструктор вызовет ошибку.
const User = (name) => { this.name = name;}const admin = new User('John Doe') // => TypeError: User is not a constructor
Аргументы
В теле традиционной функции у нас есть доступ к списку аргументов, переданных в неё.
function myFunc() { console.log(arguments);}myFunc('a', 'b'); // => ['a', 'b']
На самом деле там не массив, а массивоподобный объект, но суть в том, что у нас есть к нему доступ. Теперь давайте попробуем провернуть такой номер со стрелочной функцией.
const myFunc = () => { console.log(arguments);}myFunc('a', 'b'); // => ReferenceError: arguments is not defined
Упс, не работает. Так и запишем: у стрелочных функций нет объекта arguments
. Но это не значит, что мы не можем получить список всех переданных аргументов. Просто делать мы это будем немного иначе.
const myFunc = (...args) => { console.log(args);}myFunc('a', 'b'); // => ['a', 'b']
Возвращаемые значения
Чтобы вернуть какое-то значение из традиционной функции нам необходимо использовать выражение return
.
function myFunc() { return 42}myFunc() // => 42
Если у функции нет return
, или после return
нет никакого выражения, то она вернёт undefined
.
В стрелочной же функции возможна запись без return
.
const double = (num) => num * 2;double(2) // => 4
Т.е. стрелочная функция, будучи написанной в одну строку, без фигурных скобок, вернёт первое выражение.
Использование в качестве метода
Традиционный функции сплошь и рядом используются в качестве методов классов.
class Hero { constructor(heroName) { this.heroName = heroName; } logName() { console.log(this.heroName); }}const batman = new Hero('Batman');batman.logName(); // => Batman
Но есть одна проблема. Если мы захотим использовать этот метод в качестве callback-функции, то мы потеряем контекст.
setTimeout(batman.logName, 1000); // => undefined
А вот если использовать стрелочную функцию, то этого не произойдёт, так как у неё нет собственного контекста. И в данном случае этот как раз то, что нужно.
class Hero { constructor(heroName) { this.heroName = heroName; } logName = () => { console.log(this.heroName); }}const batman = new Hero('Batman');setTimeout(batman.logName, 1000); // => Batman
Итак, как видите, стрелочные функции достаточно сильно отличаются от традиционных функций, и есть ситуации, в которых не стоит их использовать. Равно, как есть ситуации, когда их использование оправдано и предпочтительно. Главное, что теперь, когда вы знакомы с этими особенностями, вы сможете использовать стрелочные функции осознанно и эффективно 💪.