web-dev-qa-db-fra.com

Pourquoi le guide de style Airbnb indique-t-il qu'il est déconseillé de s'appuyer sur l'inférence de nom de fonction?

// bad
class Listing extends React.Component {
  render() {
    return <div>{this.props.hello}</div>;
  }
}

// bad (relying on function name inference is discouraged)
const Listing = ({ hello }) => (
  <div>{hello}</div>
);

// good
function Listing({ hello }) {
  return <div>{hello}</div>;
}

Ceci est tiré du guide de style Airbnb react. Quelqu'un peut-il expliquer pourquoi "se fier à l'inférence de nom de fonction est découragé"? Est-ce juste une préoccupation de style?

46
xiaofan2406

Je pense que cela pourrait également avoir quelque chose à voir avec le comportement inattendu que vous pourriez rencontrer en donnant implicitement un nom lexical à ce que vous attendez d'être une fonction anonyme.

Disons par exemple que quelqu'un a compris la fonction flèche:

(x) => x+2;

Pour avoir l'équivalent de la fonction régulière:

function(x) {
  return x+2;
}

Il serait assez facile d'attendre ce code:

let foo = (x) => x+2;

Être alors l'équivalent de:

let foo = function(x) {
  return x+2;
}

Où la fonction reste anonyme et serait incapable de se référencer pour faire des choses comme la récursivité.

Donc, si alors, dans notre heureuse ignorance, quelque chose comme ça se produisait:

let foo = (x) => (x<2) ? foo(2) : "foo(1)? I should be a reference error";
console.log(foo(1));

Il fonctionnerait avec succès car cette fonction n'était évidemment pas anonyme:

let foo = function foo(x) {
  return (x<2) ? foo(2) : "foo(1)? I should be a reference error";
}  

Cela pourrait potentiellement être exacerbé par le fait que dans d'autres situations où Babel ajoute implicitement un nom à des fonctions anonymes, (ce qui, je pense, est en fait un peu un effet secondaire de la prise en charge des noms de fonctions implicites en premier lieu, bien que je puisse me tromper à ce sujet), ils gèrent correctement tous les cas Edge et lancent des erreurs de référence là où vous vous attendez.

Par exemple:

let foo = {
  bar: function() {}
} 

// Will surprisingly transpile to..

var foo = {
  bar: function bar() {}
}; 


// But doing something like:

var foo = {
  bar: function(x) {
    return (x<2) ? bar(2) : 'Whats happening!?';
  }
}

console.log(foo.bar(1));

// Will correctly cause a ReferenceError: bar is not defined

Vous pouvez vérifier 'voir compilé' sur cette démo rapide [[# #]] [~ # ~] pour voir comment Babel transpile réellement cela pour maintenir le comportement d'une fonction anonyme.


En bref, être explicite avec ce que vous faites est généralement une bonne idée car vous savez exactement à quoi vous attendre de votre code. Décourager l'utilisation de la dénomination de fonction implicite est probablement un choix stylistique à l'appui de cela, tout en restant concis et simple.

Et probablement le levage. Mais bon, un détour amusant.

27
Brad Colthurst

EDIT # 2: raison AirBnbs trouvée dans leur guide de style Javascript

N'oubliez pas de nommer l'expression - les fonctions anonymes peuvent rendre plus difficile la localisation du problème dans la pile d'appels d'une erreur ( Discussion )

Réponse originale ci-dessous

MDN a un bon aperçu de la façon dont fonctionne inférence de nom de fonction, y compris deux avertissements:

Observations

Il n'y a pas de standard <function>.name comportement d'inférence dans les deux scénarios suivants:

  1. lors de l'utilisation d'interpréteurs de scripts

L'interpréteur de script définit la propriété de nom d'une fonction uniquement si une fonction n'a pas de propriété propre appelée nom ...

  1. lors de l'utilisation de l'outillage js

Soyez prudent lorsque vous utilisez Function.name et les transformations de code source telles que celles effectuées par les compresseurs JavaScript (minificateurs) ou les obfuscateurs

....

Dans la version non compressée, le programme s'exécute dans la branche vérité et les journaux 'foo' est une instance de 'Foo' tandis que dans la version compressée, il se comporte différemment et s'exécute dans la branche else. Par conséquent, si vous vous appuyez sur Function.name comme dans l'exemple ci-dessus, assurez-vous que votre pipeline de génération ne modifie pas les noms de fonction ou ne suppose pas qu'une fonction ait un nom particulier.

Qu'est-ce que l'inférence de nom de fonction?

La propriété name renvoie le nom d'une fonction ou (avant les implémentations ES6) une chaîne vide pour les fonctions anonymes

function doSomething() {}

console.log(doSomething.name); // logs "doSomething"

Les fonctions créées avec la syntaxe new Function (...) ou simplement Function (...) ont leur propriété name définie sur une chaîne vide. Dans les exemples suivants, des fonctions anonymes sont créées, donc nom renvoie une chaîne vide

var f = function() {};
var object = {
  someMethod: function() {}
};

console.log(f.name == ''); // true
console.log(object.someMethod.name == ''); // also true

Les navigateurs qui implémentent des fonctions ES6 peuvent déduire le nom d'une fonction anonyme de sa position syntaxique. Par exemple:

var f = function() {};
console.log(f.name); // "f"

Opinion

Personnellement, je préfère les fonctions (flèche) affectées à une variable pour trois raisons fondamentales:

Tout d'abord, je n'utilise pas jamais utilise function.name

Deuxièmement, mélanger la portée lexicale des fonctions nommées avec l'affectation semble un peu lâche:

// This...
function Blah() {
   //...
}
Blah.propTypes = {
 thing: PropTypes.string
}
// ...is the same as...
Blah.propTypes = {
 thing: PropTypes.string
}
function Blah() {
   //...
}

// ALTERNATIVELY, here lexical-order is enforced
const Blah = () => {
   //...
}
Blah.propTypes = {
    thing: PropTypes.string
}

Et troisièmement, toutes choses étant égales par ailleurs, je préfère les fonctions fléchées:

  • communiquer au lecteur qu'il n'y a pas de this, pas de arguments etc.
  • regarde mieux (à mon humble avis)
  • performances (la dernière fois que j'ai regardé, les fonctions fléchées étaient légèrement plus rapides)

EDIT: instantanés de mémoire

J'écoutais un Podcast et un invité a parlé d'une situation où il devait faire face aux limites de l'utilisation des fonctions fléchées avec le profilage de la mémoire, j'ai été dans le exactement la même chose = situation antérieure.

Actuellement, les instantanés de mémoire n'incluront pas de nom de variable - vous pourriez donc vous retrouver à convertir des fonctions fléchées en fonctions nommées juste pour brancher le profileur de mémoire. Mon expérience a été assez simple et je suis toujours satisfait des fonctions fléchées.

De plus, je n'ai utilisé des instantanés de mémoire qu'une seule fois, donc je me sens à l'aise de renoncer à quelque "instrumention" pour la clarté (subjective) par défaut.

23
Ashley Coolman

Ceci est dû au fait:

const Listing = ({ hello }) => (
  <div>{hello}</div>
);

a un nom inféré de Listing, alors qu'il semble que vous le nommiez, vous n'êtes en fait pas:

Exemple

// we know the first three ways already...

let func1 = function () {};
console.log(func1.name); // func1

const func2 = function () {};
console.log(func2.name); // func2

var func3 = function () {};
console.log(func3.name); // func3

et ça?

const bar = function baz() {
    console.log(bar.name); // baz
    console.log(baz.name); // baz
};

function qux() {
  console.log(qux.name); // qux
}
4
JordanHendrix

Comme tout autre guide de style, Airbnb est avisé et n'est pas toujours bien raisonné.

Fonction name propriété n'est pas censé être utilisé pour autre chose que le débogage dans une application côté client car le nom d'origine de la fonction est perdu pendant la minification. Quant au débogage, il devient moins efficace si une fonction n'a pas de nom significatif dans la pile d'appels, il est donc avantageux de le conserver dans certains cas.

Une fonction obtient name avec à la fois une définition de fonction comme function Foo = () => {} et une fonction nommée expression comme une flèche dans const Foo = () => {}. Il en résulte que la fonction Foo a un nom donné, Foo.name === 'Foo'.

Certains transpilers suivent la spécification. Babel transpile ce code en ES5:

var Foo = function Foo() {};

Et TypeScript rompt la spécification:

var Foo = function () {};

Cela ne signifie pas que l'expression de la fonction nommée est mauvaise et doit être découragée. Tant qu'un transpilateur est conforme aux spécifications ou que le nom de la fonction n'a pas d'importance, cette préoccupation peut être écartée.

Le problème est applicable aux applications transpilées. Cela dépend d'un transpilateur utilisé et de la nécessité de conserver la propriété function name. Le problème n'existe pas dans ES6 natif.

2
Estus Flask