CODEGURU

Особенности стрелочных функций

ES2015 принёс нам новый способ работы с функциями — стрелочные функции. И хотя уже прошло практически 5 лет, многие до сих пор до конца не понимают их особенностей и чем они отличаются от обычных функций, когда их можно или даже нужно использовать, а когда лучше от них отказаться. Вот об этом и поговорим.

Для начала давайте определимся с основными характеристиками, в которых стрелочные функции имеют особенности и отличия от традиционных функций:

  1. Контекст (он же this)
  2. Функция-конструктор
  3. Аргументы
  4. Возвращаемые значения
  5. Использование в качестве метода

И давайте пойдём по порядку.

Контекст (он же 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

Итак, как видите, стрелочные функции достаточно сильно отличаются от традиционных функций, и есть ситуации, в которых не стоит их использовать. Равно, как есть ситуации, когда их использование оправдано и предпочтительно. Главное, что теперь, когда вы знакомы с этими особенностями, вы сможете использовать стрелочные функции осознанно и эффективно 💪.