Как и когда использовать функции стрелок JavaScript

Стандарт ES2015 ввел в JavaScript функции со стрелками. Функции со стрелками имеют более простой синтаксис, чем стандартные функции, но мы также увидим, что в их поведении есть некоторые важные различия.

Что такое функции стрелок JavaScript?

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

Ниже мы подробно рассмотрим функции со стрелками, но в целом их следует избегать всякий раз, когда вам требуется новая привязка this. Функции со стрелками не имеют собственного this; они наследуют this от внешней области видимости.

Функции со стрелками также нельзя использовать в качестве конструкторов или функций-генераторов, поскольку они не могут содержать инструкцию yield.

Каков основной синтаксис функции стрелок JavaScript?

Функция со стрелкой состоит из списка аргументов, за которым следует стрелка (состоящая из знака равенства и знака большего, чем (=>)), а затем текст функции. Вот простой пример функции со стрелкой, которая принимает один аргумент:

const greet = name => {
  console.log(`Hello, ${name}!`);
};

При желании вы также можете заключить аргумент в круглые скобки:

const greet = (name) => {
  console.log(`Hello, ${name}!`);
}

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

const sum = (a, b) => {
  return a + b;
}

Анонимная функция со стрелкой не имеет имени. Обычно они передаются как функции обратного вызова:

button.addEventListener('click', event => {
  console.log('You clicked the button!');
});

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

const greet = name => console.log(`Hello, ${name}!`);

Неявные возвраты и функция стрелок JavaScript

Одним из важных отличий функций со стрелками JavaScript от стандартных функций является идея неявного возврата: возврата значения без использования инструкции return.

Если вы опустите фигурные скобки в функции со стрелками, значение выражения в теле функции будет возвращено из функции без использования инструкции return. Давайте вернемся к функции sum, описанной ранее. Это можно переписать, чтобы использовать неявный return:

const sum = (a, b) => a + b;

Неявный возврат удобен при создании функций обратного вызова:

const values = [1, 2, 3];
const doubled values = values.map(value => value * 2); // [2, 4, 6]

Возвращающий объект неявно

Вы можете вернуть любое значение, которое хотите, с помощью неявного возврата, но вам потребуется небольшая дополнительная помощь, если вы хотите вернуть объект. Поскольку объектный литерал использует фигурные скобки, JavaScript интерпретирует фигурные скобки как тело функции. Рассмотрим этот пример:

const createUser = (name, email) => { name, email };

В этом случае неявного возврата не будет, и функция фактически вернет значение undefined, поскольку нет инструкции return. Чтобы вернуть объект неявно, вам нужно заключить объект в круглые скобки:

const createUser = (name, email) => ({ name, email });

Теперь JavaScript знает, что это неявное возвращение объекта, содержащего имя и свойства электронной почты.

Явные возвраты и функция стрелок JavaScript

Как и в случае со стандартными функциями, функция со стрелкой может явно возвращать значение с помощью инструкции return:

const createUser = (name, email) => {
  return { name, email };
};

Чем функции стрелок JavaScript отличаются от стандартных функций

Функции со стрелками ведут себя иначе, чем стандартные функции, в некоторых других отношениях.

Нет этой привязки

Самое существенное отличие заключается в том, что, в отличие от стандартной функции, функция arrow не создает собственную привязку this. Рассмотрим следующий пример:

const counter = {
  value: 0,
  increment: () => {
    this.value += 1;
  }
};

Поскольку метод increment является функцией со стрелками, значение THIS в функции не ссылается на объект counter. Вместо этого оно наследует внешний объект this, который в этом примере был бы глобальным объектом window.

Как и следовало ожидать, если вы вызовете counter.increment(), это не изменит значение счетчика. Вместо этого это значение будет неопределенным, поскольку оно относится к окну.

Иногда вы можете использовать это в своих интересах. Бывают случаи, когда вам действительно нужно вывести значение this из функции. Это распространенный сценарий при использовании функций обратного вызова. Прежде чем начать работать со стрелками, вам нужно было бы вызвать bind для функции, чтобы заставить ее иметь определенное значение this, или вы могли бы следовать такому шаблону, как этот:

var self = this;
setTimeout(function() {
  console.log(self.name);
}, 1000);

С помощью функции со стрелкой вы получаете это из окружающей области:

setTimeout(() => console.log(this.name));

Возражение без аргументов

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

Рассмотрим эту функцию sum, которая поддерживает переменное количество аргументов:

function sum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }

  return total;
}

Вы можете вызвать sum с любым количеством аргументов:

sum(1, 2, 3) // 6

Если вы реализуете sum как функцию со стрелкой, объекта arguments не будет. Вместо этого вам нужно будет использовать синтаксис параметра rest:

const sum = (...args) => {
  let total = 0;
  for (let i = 0; i < args.length; i++) {
    total += args[i];
  }

  return args;
}

Вы можете вызвать эту версию функции sum таким же образом:

sum(1, 2, 3) // 6

Конечно, этот синтаксис не уникален для функций со стрелками. Вы также можете использовать синтаксис параметров rest для стандартных функций. Исходя из моего опыта работы с современным JavaScript, я на самом деле больше не вижу, чтобы объект arguments использовался, так что это различие может быть спорным.

Нет прототипа

Стандартные функции JavaScript имеют свойство prototype. До появления синтаксиса class это был способ создания объектов с новыми:

function Greeter() { }
Greeter.prototype.sayHello = function(name) {
  console.log(`Hello, ${name}!`);
};

new Greeter().sayHello('Joe'); // Hello, Joe!

Если вы попробуете это с помощью функции со стрелками, вы получите сообщение об ошибке. Это связано с тем, что функции со стрелками не имеют прототипа:

const Greeter = () => {};
Greeter.prototype.sayHello = name => console.log(`Hello, ${name}!`);
// TypeError: Cannot set properties of undefined (setting 'sayHello')

Когда использовать функции со стрелками JavaScript в сравнении со стандартными функциями

Функции со стрелками можно использовать во многих сценариях, но есть некоторые ситуации, когда вам все равно нужно использовать стандартное функциональное выражение. К ним относятся:

  • Конструктор класса
  • Объектный метод, в котором вам нужно получить доступ к объекту через значение this
  • Функция, которую вам нужно явно привязать к заданному значению this с помощью Function.prototype.bind
  • Функция-генератор, содержащая инструкции yield

Функции со стрелками особенно хороши при использовании в качестве функций обратного вызова из-за их лаконичного синтаксиса. В частности, они очень полезны для таких методов работы с массивами, как forEach, map и filter. Вы можете использовать их как объектные методы, но только в том случае, если метод не пытается получить доступ к объекту с помощью этого.

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

Как определить метод с помощью функции со стрелкой

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

class Person {
  constructor(name) {
    this.name = name;
  }

  greet = () => console.log(`Hello, ${this.name}!`);
}

В отличие от метода для объектного литерала, который, как мы видели ранее, не получает значение this, здесь метод greet получает значение this из включающего его экземпляра Person. Тогда, независимо от того, как вызывается метод, значение this всегда будет являться экземпляром класса. Рассмотрим этот пример, в котором используется стандартный метод с параметром setTimeout:

class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
      console.log(`Hello, ${this.name}!`);
  }

  delayedGreet() {
      setTimeout(this.greet, 1000);
  }
}

new Person('Joe').delayedGreet(); // Hello, undefined!

Когда метод greet вызывается из вызова setTimeout, его значение this становится глобальным объектом window. Свойство name в нем не определено, поэтому вы получите сообщение Hello, undefined! когда вы вызываете метод delayedGreet.

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

class Person {
    constructor(name) {
      this.name = name;
    }

    greet = () => console.log(`Hello, ${this.name}!`);

    delayedGreet() {
        setTimeout(this.greet, 1000);
    }
}

new Person('Joe').delayedGreet(); // Hello, Joe!

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

class Person {
  constructor = name => {
    this.name = name;
  }
}

// SyntaxError: Classes may not have a field named 'constructor'

Вывод

С появлением стандарта ES2015 программисты на JavaScript получили в свой набор инструментов функции со стрелками. Их главная сила - в сокращенном синтаксисе; вам не нужно ключевое слово function, а при использовании неявного return вам не нужен оператор return.

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

Рассмотрим эту цепочку операций с массивами:

const numbers = [1, 2, 3, 4]
  .map(function(n) {
    return n * 3;
  })
  .filter(function(n) {
    return n % 2 === 0;
  });

Это выглядит хорошо, но немного многословно. С функциями со стрелками синтаксис более понятен:

const numbers = [1, 2, 3, 4]
  .map(n => n * 3)
  .filter(n => n % 2 === 0);

Функции со стрелками не имеют объекта arguments, но они поддерживают синтаксис параметров rest. Это упрощает создание функций со стрелками, которые принимают переменное количество аргументов.

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