Особенности стрелочных функций
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
Итак, как видите, стрелочные функции достаточно сильно отличаются от традиционных функций, и есть ситуации, в которых не стоит их использовать. Равно, как есть ситуации, когда их использование оправдано и предпочтительно. Главное, что теперь, когда вы знакомы с этими особенностями, вы сможете использовать стрелочные функции осознанно и эффективно 💪.