Les «fonctions fléchées» et les «fonctions» sont-elles équivalentes / échangeables?

521

Les fonctions fléchées d'ES2015 fournissent une syntaxe plus concise.

  • Puis-je remplacer toutes mes déclarations / expressions de fonction par des fonctions fléchées maintenant?
  • À quoi dois-je faire attention?

Exemples:

Fonction constructeur

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

// vs

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

Méthodes prototypes

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

// vs

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

Méthodes d'objets (littérales)

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

// vs

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

Rappels

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

// vs

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

Fonctions variadiques

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

// vs
const sum = (...args) => {
  // ...
};
Felix Kling
la source
5
Des questions similaires sur les fonctions fléchées se posent de plus en plus avec ES2015 qui devient plus populaire. Je ne pensais pas qu'il y avait une bonne question / réponse canonique pour ce problème, j'ai donc créé celui-ci. Si vous pensez qu'il y en a déjà un bon, faites-le moi savoir et je fermerai celui-ci en double ou le supprimerez. N'hésitez pas à améliorer les exemples ou à en ajouter de nouveaux.
Felix Kling
2
Qu'en est-il de JavaScript ecma6 changer la fonction normale en fonction flèche ? Bien sûr, une question normale ne peut jamais être aussi bonne et générique qu'une question spécifiquement écrite pour être canonique.
Bergi
Regardez cet exemple Plnkr La variable thisest très différente timesCalledpar incréments de 1 seulement à chaque fois que le bouton est appelé. Ce qui répond à ma question personnelle: .click( () => { } )et les .click(function() { }) deux créent le même nombre de fonctions lorsqu'ils sont utilisés dans une boucle comme vous pouvez le voir dans le décompte Guid dans le Plnkr.
abbaf33f

Réponses:

750

tl; dr: Non! Les fonctions fléchées et les déclarations / expressions de fonction ne sont pas équivalentes et ne peuvent pas être remplacées à l'aveugle.
Si la fonction que vous souhaitez remplacer ne pas utiliser this, argumentset n'est pas appelé avec new, alors oui.


Comme souvent: cela dépend . Les fonctions fléchées ont un comportement différent de celui des déclarations / expressions de fonction, alors examinons d'abord les différences:

1. Lexique thisetarguments

Les fonctions fléchées n'ont ni leur propre thisni leur argumentsliaison. Au lieu de cela, ces identificateurs sont résolus dans la portée lexicale comme toute autre variable. Cela signifie qu'à l'intérieur d'une fonction flèche, thiset se argumentsréférer aux valeurs de thiset argumentsdans l'environnement, la fonction flèche est définie dans (c'est-à-dire "à l'extérieur" de la fonction flèche):

// 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

Dans le cas de l'expression de fonction, thisfait référence à l'objet qui a été créé à l'intérieur du createObject. Dans le cas de la fonction flèche, se thisréfère à lui this- createObjectmême.

Cela rend les fonctions fléchées utiles si vous devez accéder à l' thisenvironnement actuel:

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

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

Notez que cela signifie également qu'il n'est pas possible de définir une fonction de flèche thisavec .bindou .call.

Si vous n'êtes pas très familier this, pensez à lire

2. Les fonctions fléchées ne peuvent pas être appelées avec new

ES2015 établit une distinction entre les fonctions qui appellent mesure et les fonctions qui sont construire en mesure. Si une fonction est constructible, elle peut être appelée avec new, ie new User(). Si une fonction peut être appelée, elle peut être appelée sans new(c'est-à-dire un appel de fonction normal).

Les fonctions créées par des déclarations / expressions de fonction sont à la fois constructibles et appelables.
Les fonctions fléchées (et méthodes) ne peuvent être appelées. classles constructeurs sont uniquement constructibles.

Si vous essayez d'appeler une fonction non appelable ou de construire une fonction non constructible, vous obtiendrez une erreur d'exécution.


Sachant cela, nous pouvons affirmer ce qui suit.

Remplaçable:

  • Fonctions qui n'utilisent pas thisou arguments.
  • Fonctions utilisées avec .bind(this)

Non remplaçable:

  • Fonctions constructeur
  • Fonction / méthodes ajoutées à un prototype (car elles utilisent généralement this)
  • Fonctions variadiques (si elles utilisent arguments(voir ci-dessous))

Regardons cela de plus près en utilisant vos exemples:

Fonction constructeur

Cela ne fonctionnera pas car les fonctions fléchées ne peuvent pas être appelées avec new. Continuez à utiliser une déclaration / expression de fonction ou à utiliser class.

Méthodes prototypes

Probablement pas, car les méthodes prototypes sont généralement utilisées thispour accéder à l'instance. S'ils n'utilisent pas this, vous pouvez le remplacer. Cependant, si vous vous souciez principalement de la syntaxe concise, utilisez-la classavec sa syntaxe de méthode concise:

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

  getName() {
    return this.name;
  }
}

Méthodes d'objet

De même pour les méthodes dans un objet littéral. Si la méthode veut référencer l'objet lui-même via this, continuez à utiliser des expressions de fonction ou utilisez la nouvelle syntaxe de la méthode:

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

Rappels

Ça dépend. Vous devez absolument le remplacer si vous aliasez l'extérieur thisou utilisez .bind(this):

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

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

Mais: si le code qui appelle le rappel définit explicitement thisune valeur spécifique, comme c'est souvent le cas avec les gestionnaires d'événements, en particulier avec jQuery, et que le rappel utilise this(ou arguments), vous ne pouvez pas utiliser une fonction de flèche!

Fonctions variadiques

Étant donné que les fonctions fléchées n'ont pas les leurs arguments, vous ne pouvez pas simplement les remplacer par une fonction fléchée. Cependant, ES2015 introduit une alternative à l'utilisation arguments: le paramètre reste .

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

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

Question connexe:

Autres ressources:

Felix Kling
la source
6
Il vaut peut-être la peine de mentionner que le lexical thisaffecte également superet qu'il n'a pas .prototype.
loganfsmyth
1
Il serait également bon de mentionner qu'elles ne sont pas interchangeables syntaxiquement - une fonction flèche ( AssignmentExpression) ne peut pas simplement être supprimée partout où une expression de fonction ( PrimaryExpression) le peut et elle déclenche les gens assez fréquemment (surtout depuis qu'il y a eu analyse erreurs dans les implémentations JS majeures).
JMM
@JMM: "ça fait voyager les gens assez fréquemment" pouvez-vous donner un exemple concret? En survolant la spécification, il semble que les endroits où vous pouvez mettre un FE mais pas un AF entraîneraient de toute façon des erreurs d'exécution ...
Felix Kling
Bien sûr, je veux dire des choses comme essayer d'appeler immédiatement une fonction flèche comme une expression de fonction ( () => {}()) ou faire quelque chose comme x || () => {}. C'est ce que je veux dire: les erreurs d'exécution (analyse). (Et même si c'est le cas, les gens pensent assez souvent que l'erreur est erronée.) Essayez-vous simplement de couvrir les erreurs logiques qui passeraient inaperçues car elles ne font pas nécessairement d'erreur lorsqu'elles sont analysées ou exécutées? new'un est une erreur d'exécution, non?
JMM
Voici quelques liens à venir dans la nature: substack / node-browserify # 1499 , babel / babel-eslint # 245 (il s'agit d'une flèche asynchrone, mais je pense que c'est le même problème de base), et un tas de problèmes sur Babel qui sont difficiles à trouver maintenant, mais voici un T2847 .
JMM
11

Fonctions fléchées => meilleure fonctionnalité ES6 jusqu'à présent. Ils sont un ajout extrêmement puissant à ES6, que j'utilise constamment.

Attendez, vous ne pouvez pas utiliser la fonction flèche partout dans votre code, cela ne fonctionnera pas dans tous les cas, comme thislorsque les fonctions flèche ne sont pas utilisables. Sans aucun doute, la fonction flèche est un excellent ajout, elle apporte la simplicité du code.

Mais vous ne pouvez pas utiliser une fonction de flèche lorsqu'un contexte dynamique est requis: définir des méthodes, créer des objets avec des constructeurs, en tirer la cible lors de la gestion des événements.

Les fonctions fléchées ne doivent PAS être utilisées car:

  1. Ils n'ont pas this

    Il utilise la «portée lexicale» pour déterminer quelle thisdevrait être la valeur de « ». Dans un champ lexical de mots simples, il utilise « this» de l'intérieur du corps de la fonction.

  2. Ils n'ont pas arguments

    Les fonctions fléchées n'ont pas d' argumentsobjet. Mais la même fonctionnalité peut être obtenue en utilisant des paramètres de repos.

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

  3. Ils ne peuvent pas être utilisés avec new

    Les fonctions fléchées ne peuvent pas être des interprètes car elles n'ont pas de propriété prototype.

Quand utiliser la fonction flèche et quand non:

  1. Ne pas utiliser pour ajouter une fonction en tant que propriété dans un littéral objet car nous ne pouvons pas y accéder.
  2. Les expressions de fonction sont les meilleures pour les méthodes d'objet. Catégorie fonctions sont les meilleurs pour callbacks ou des méthodes telles que map, reduceou forEach.
  3. Utilisez des déclarations de fonctions pour les fonctions que vous appelleriez par nom (car elles sont hissées).
  4. Utilisez les fonctions fléchées pour les rappels (car elles ont tendance à être terser).
Ashutosh
la source
2
les 2. Ils n'ont pas d'arguments, je suis désolé, ce n'est pas vrai, on peut avoir des arguments sans utiliser l'opérateur ..., peut-être voulez-vous dire que vous n'avez pas de tableau comme argument
Carmine Tambascia
@CarmineTambascia Lisez à propos de l' argumentsobjet spécial qui n'est pas disponible dans les fonctions fléchées ici: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
vichle
0

Pour utiliser les fonctions fléchées avec function.prototype.call, j'ai créé une fonction d'aide sur le prototype d'objet:

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

usage

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

Éditer

Vous n'avez pas besoin d'aide. Vous pourriez faire:

var obj = {f:3, a:2}
(This => This.f + This.a).call(undefined, obj); // 5
toddmo
la source