«Функции стрелок» и «Функции» эквивалентны / взаимозаменяемы?

521

Функции стрелок в ES2015 обеспечивают более краткий синтаксис.

  • Могу ли я заменить все мои объявления / выражения функций функциями стрелок сейчас?
  • Что я должен высматривать?

Примеры:

Функция конструктора

function User(name) {
  this.name = name;
}

// vs

const User = name => {
  this.name = name;
};

Методы прототипа

User.prototype.getName = function() {
  return this.name;
};

// vs

User.prototype.getName = () => this.name;

Объектные (буквальные) методы

const obj = {
  getName: function() {
    // ...
  }
};

// vs

const obj = {
  getName: () => {
    // ...
  }
};

Callbacks

setTimeout(function() {
  // ...
}, 500);

// vs

setTimeout(() => {
  // ...
}, 500);

Вариадические функции

function sum() {
  let args = [].slice.call(arguments);
  // ...
}

// vs
const sum = (...args) => {
  // ...
};
Феликс Клинг
источник
5
Подобные вопросы о функциях стрелок возникают все больше и больше с ES2015 становится все более популярным. Я не чувствовал, что был хороший канонический вопрос / ответ на этот вопрос, поэтому я создал этот. Если вы считаете, что он уже есть, сообщите мне, и я закрою его как дубликат или удалю. Не стесняйтесь улучшать примеры или добавлять новые.
Феликс Клинг
2
Как насчет JavaScript ecma6 заменить обычную функцию на функцию стрелки ? Конечно, нормальный вопрос никогда не может быть настолько хорошим и общим, как тот, который специально написан как канонический.
Берги
Посмотрите на этот пример Plnkr. При каждом нажатии кнопки переменная thisочень сильно timesCalledувеличивается на 1. Который отвечает на мой личный вопрос: .click( () => { } )и .click(function() { }) обе создают одинаковое количество функций при использовании в цикле, как вы можете видеть из подсчета Guid в Plnkr.
abbaf33f

Ответы:

750

ДЛ: Нет! Функции стрелок и объявления / выражения функций не эквивалентны и не могут быть заменены вслепую.
Если функция, которую вы хотите заменить, не используетthis , argumentsа не вызывается new, то да.


Как так часто: это зависит . Функции со стрелками ведут себя иначе, чем объявления / выражения функций, поэтому давайте сначала посмотрим на различия:

1. Лексический thisиarguments

Функции стрелок не имеют своих собственных thisили argumentsобязательных. Вместо этого эти идентификаторы разрешаются в лексической области, как и любая другая переменная. Это означает, что внутри функции стрелки thisи argumentsв отношении значений thisи argumentsв среде, в которой определена функция стрелки (т. Е. «Вне» функции стрелки):

// Example using a function expression
function createObject() {
  console.log('Inside `createObject`:', this.foo);
  return {
    foo: 42,
    bar: function() {
      console.log('Inside `bar`:', this.foo);
    },
  };
}

createObject.call({foo: 21}).bar(); // override `this` inside createObject

// Example using a arrow function
function createObject() {
  console.log('Inside `createObject`:', this.foo);
  return {
    foo: 42,
    bar: () => console.log('Inside `bar`:', this.foo),
  };
}

createObject.call({foo: 21}).bar(); // override `this` inside createObject

В случае выражения функции thisссылается на объект, который был создан внутри createObject. В функции стрелка случае, thisотносится к thisо createObjectсебе.

Это делает функции стрелок полезными, если вам нужен доступ thisк текущей среде:

// currently common pattern
var that = this;
getData(function(data) {
  that.data = data;
});

// better alternative with arrow functions
getData(data => {
  this.data = data;
});

Обратите внимание , что это также означает , что является не возможно установить функцию Эрроуthis с .bindили .call.

Если вы не очень знакомы с this, подумайте о чтении

2. Функции стрелок не могут быть вызваны с new

ES2015 различает функции, вызов в состоянии и функции, которые сооружать состоянии. Если функция конструируема, ее можно вызвать с помощью new, т.е.new User() . Если функция вызывается, она может быть вызвана без new(т.е. нормальный вызов функции).

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

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


Зная это, мы можем утверждать следующее.

Сменная:

  • Функции, которые не используют this или arguments.
  • Функции, которые используются с .bind(this)

Не подлежит замене:

  • Функции конструктора
  • Функция / методы, добавленные в прототип (потому что они обычно используют this )
  • Variadic функции (если они используют arguments(см. Ниже))

Давайте посмотрим на это подробнее, используя ваши примеры:

Функция конструктора

Это не будет работать, потому что функции стрелок не могут быть вызваны с new. Продолжайте использовать объявление функции / выражение или использование class.

Методы прототипа

Скорее всего, нет, потому что методы-прототипы обычно используют thisдля доступа к экземпляру. Если они не используют this, то вы можете заменить его. Однако, если вы в первую очередь заботитесь о кратком синтаксисе, используйте classего с кратким синтаксисом метода:

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

  getName() {
    return this.name;
  }
}

Методы объекта

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

const obj = {
  getName() {
    // ...
  },
};

Callbacks

Это зависит. Вы обязательно должны заменить его, если вы используете псевдонимы внешнего интерфейса thisили используете .bind(this):

// old
setTimeout(function() {
  // ...
}.bind(this), 500);

// new
setTimeout(() => {
  // ...
}, 500);

Но: если код, который вызывает обратный вызов, явно устанавливает thisопределенное значение, как это часто бывает в обработчиках событий, особенно в jQuery, и обратный вызов использует this(или arguments), вы не можете использовать функцию стрелки!

Вариадические функции

Так как функции стрелок не имеют своих собственных arguments, вы не можете просто заменить их функцией стрелок. Однако ES2015 представляет альтернативу использованию arguments: параметр rest .

// old
function sum() {
  let args = [].slice.call(arguments);
  // ...
}

// new
const sum = (...args) => {
  // ...
};

Связанный вопрос:

Другие ресурсы:

Феликс Клинг
источник
6
Возможно, стоит упомянуть, что лексическое thisтакже влияет, superа что нет .prototype.
loganfsmyth
1
Также было бы хорошо упомянуть, что они не являются синтаксически взаимозаменяемыми - функция стрелки ( AssignmentExpression) не может быть просто вставлена ​​везде, где может быть выражение функции ( PrimaryExpression), и это довольно часто запутывает людей (особенно, если был разбор ошибки в основных реализациях JS).
JMM
@JMM: «это часто сбивает людей с толку », можете ли вы привести конкретный пример? Пролистывая спецификацию, кажется, что места, где вы можете поместить FE, но не AF, в любом случае приведут к ошибкам во время выполнения ...
Феликс Клинг
Конечно, я имею в виду такие вещи, как попытка немедленно вызвать функцию стрелки, например выражение функции ( () => {}()), или сделать что-то подобное x || () => {}. Вот что я имею в виду: ошибки времени выполнения (синтаксический анализ). (И хотя это так, довольно часто люди думают, что ошибка в ошибке.) Вы просто пытаетесь скрыть логические ошибки, которые остались бы незамеченными, потому что они не обязательно дают ошибку при разборе или выполнении? newОдин из них является ошибкой во время выполнения, верно?
JMM
Вот некоторые ссылки на него, появляющиеся в дикой природе: substack / node-browserify # 1499 , babel / babel-eslint # 245 (это асинхронная стрелка, но я думаю, что это та же самая основная проблема), и множество проблем на Вавилон, который трудно найти сейчас, но вот один T2847 .
JMM
11

Стрелка функции => лучшая функция ES6 до сих пор. Они являются чрезвычайно мощным дополнением к ES6, которое я постоянно использую.

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

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

Функции стрелок НЕ должны использоваться, потому что:

  1. Они не имеют this

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

  2. Они не имеют arguments

    Функции стрелок не имеют argumentsобъекта. Но та же функциональность может быть достигнута с помощью параметров отдыха.

    let sum = (...args) => args.reduce((x, y) => x + y, 0) sum(3, 3, 1) // output - 7 `

  3. Они не могут быть использованы с new

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

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

  1. Не используйте для добавления функции в качестве свойства в литерал объекта, потому что мы не можем получить к нему доступ.
  2. Выражения функций лучше всего подходят для объектных методов. Стрелка функции лучше всего подходят для обратных вызовов или методов , таких как map, reduceили forEach.
  3. Используйте объявления функций для функций, которые вы вызываете по имени (потому что они подняты).
  4. Используйте функции стрелок для обратных вызовов (потому что они имеют тенденцию быть более краткими).
Ashutosh
источник
2
2. У них нет аргументов, извините, это не так, можно использовать аргумент без использования оператора ..., может быть, вы хотите сказать, что у вас нет массива в качестве аргумента
Кармин Тамбассия
@CarmineTambascia Прочтите о специальном argumentsобъекте, который недоступен в функциях стрелок, здесь: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
vichle
0

Чтобы использовать функции со стрелками function.prototype.call, я создал вспомогательную функцию для прототипа объекта:

  // Using
  // @func = function() {use this here} or This => {use This here}
  using(func) {
    return func.call(this, this);
  }

Применение

  var obj = {f:3, a:2}
  .using(This => This.f + This.a) // 5

редактировать

Вам не нужен помощник. Вы могли бы сделать:

var obj = {f:3, a:2}
(This => This.f + This.a).call(undefined, obj); // 5
toddmo
источник