web-dev-qa-db-fra.com

Quels sont les avantages d'utiliser des fonctions anonymes au lieu des fonctions nommées pour les rappels et les paramètres dans le code d'événement JavaScript?

Je suis novice en JavaScript. Je comprends de nombreux concepts du langage, j'ai lu sur le modèle d'héritage du prototype et je siffle avec de plus en plus de choses frontales interactives. C'est un langage intéressant, mais je suis toujours un peu découragé par les spaghettis de rappel qui sont typiques de nombreux modèles d'interaction non triviaux.

Quelque chose qui m'a toujours semblé étrange, c'est qu'en dépit du cauchemar de lisibilité qui est un nid de rappels imbriqués JavaScript, la seule chose que je vois très rarement dans de nombreux exemples et tutoriels est l'utilisation de fonctions nommées prédéfinies comme arguments de rappel. Je suis un Java par jour, et en rejetant les trucs stéréotypés sur les noms Enterprise-y pour les unités de code l'une des choses que j'ai appréciées travailler dans un langage avec une forte sélection d'IDE fonctionnels est que l'utilisation de noms significatifs, s'ils sont longs, peut rendre l'intention et la signification du code beaucoup plus clairs sans rendre plus difficile d'être réellement productif. Alors pourquoi ne pas utiliser la même approche lors de l'écriture de JavaScript code?

En y réfléchissant, je peux trouver des arguments qui sont à la fois pour et contre cette idée, mais ma naïveté et ma nouveauté dans le langage m'empêchent de tirer des conclusions quant à la raison pour laquelle ce serait bon au niveau technique.

Avantages:

  • La flexibilité. Une fonction asynchrone avec un paramètre de rappel peut être atteinte par l'un des nombreux chemins de code différents et il peut être difficile d'avoir à écrire une fonction nommée pour tenir compte de chaque cas Edge possible.
  • La vitesse. Il joue fortement dans la mentalité de pirate. Fixez les choses jusqu'à ce que cela fonctionne.
  • Tout le monde le fait
  • Des fichiers plus petits, même si cela est trivial, mais chaque bit compte sur le Web.
  • AST plus simple? Je suppose que les fonctions anonymes sont générées au moment de l'exécution et donc le JIT ne va pas se moquer de mapper le nom aux instructions, mais je ne fais que deviner à ce stade.
  • Envoi plus rapide? Pas sûr de celui-ci non plus. Devinez encore.

Les inconvénients:

  • C'est hideux et illisible
  • Cela ajoute à la confusion lorsque vous êtes imbriqué au fond d'un marécage de rappels (ce qui, pour être juste, signifie probablement que vous écrivez du code mal construit pour commencer, mais c'est assez courant).
  • Pour quelqu'un sans expérience fonctionnelle, ce peut être un concept bizarre de grogner

Avec autant de navigateurs modernes montrant la possibilité d'exécuter du code JavaScript beaucoup plus rapidement qu'auparavant, je n'arrive pas à voir comment toute sorte de gain de performance trivial que l'on pourrait obtenir en utilisant des rappels anonymes serait une nécessité. Il semble que, si vous êtes dans une situation où l'utilisation d'une fonction nommée est possible (comportement prévisible et chemin d'exécution), il n'y a aucune raison de ne pas le faire.

Y a-t-il des raisons techniques ou des accrochages dont je ne suis pas au courant qui rendent cette pratique si courante pour une raison?

41
Doug Stephen

J'utilise des fonctions anonymes pour trois raisons:

  1. Si aucun nom n'est nécessaire parce que la fonction n'est appelée qu'au même endroit, alors pourquoi ajouter un nom à l'espace de noms dans lequel vous vous trouvez.
  2. Les fonctions anonymes sont déclarées en ligne et les fonctions en ligne présentent des avantages en ce qu'elles peuvent accéder aux variables dans les étendues parent. Oui, vous pouvez mettre un nom sur une fonction anonyme, mais cela est généralement inutile si elle est déclarée en ligne. Donc en ligne a un avantage significatif et si vous faites en ligne, il n'y a pas de raison d'y mettre un nom.
  3. Le code semble plus autonome et lisible lorsque les gestionnaires sont définis directement dans le code qui les appelle. Vous pouvez lire le code de manière presque séquentielle plutôt que d'avoir à rechercher la fonction avec ce nom.

J'essaie d'éviter l'imbrication profonde des fonctions anonymes car cela peut être difficile à comprendre et à lire. Habituellement, lorsque cela se produit, il existe une meilleure façon de structurer le code (parfois avec une boucle, parfois avec une table de données, etc.) et les fonctions nommées ne sont généralement pas la solution là non plus.

Je suppose que j'ajouterais que si un rappel commence à avoir plus de 15 à 20 lignes et qu'il n'a pas besoin d'un accès direct aux variables dans la portée parent, je serais tenté de lui donner un nom et de le décomposer en c'est sa propre fonction nommée déclarée ailleurs. Il y a certainement un point de lisibilité ici où une fonction non triviale qui devient longue est juste plus maintenable si elle est placée dans sa propre unité nommée. Mais, la plupart des rappels avec lesquels je me retrouve ne sont pas si longs et je trouve plus lisible de les garder en ligne.

44
jfriend00

Je préfère les fonctions nommées moi-même, mais pour moi, cela se résume à une question:

Vais-je utiliser cette fonction ailleurs?

Si la réponse est oui, je la nomme/la définis. Sinon, passez-le comme une fonction anonyme.

Si vous ne l'utilisez qu'une seule fois, cela n'a aucun sens de surcharger l'espace de noms global avec lui. Dans les frontaux complexes d'aujourd'hui, le nombre de fonctions nommées qui auraient pu être anonymes augmente rapidement (facilement plus de 1000 sur des conceptions vraiment complexes), entraînant des gains de performances (relativement) importants en préférant les fonctions anonymes.

Cependant, la maintenabilité du code est également extrêmement importante. Chaque situation est différente. Si vous n'écrivez pas beaucoup de ces fonctions pour commencer, il n'y a aucun mal à le faire de toute façon. C'est vraiment à votre préférence.

Une autre note sur les noms. Prendre l'habitude de définir des noms longs nuira vraiment à la taille de votre fichier. Prenons l'exemple suivant.

Supposons que ces deux fonctions font la même chose:

function addTimes(time1, time2)
{
    // return time1 + time2;
}

function addTwoTimesIn24HourFormat(time1, time2)
{
    // return time1 + time2;
}

Le second vous indique exactement ce qu'il fait dans le nom. Le premier est plus ambigu. Cependant, il y a 17 caractères de différence dans le nom. Supposons que la fonction soit appelée 8 fois dans le code, c'est 153 octets supplémentaires votre code n'avait pas besoin d'avoir. Pas colossal, mais si c'est une habitude, extrapoler cela à 10 ou même 100 de fonctions signifiera facilement quelques Ko de différence dans le téléchargement.

Encore une fois cependant, la maintenabilité doit être mise en balance avec les avantages des performances. C'est la peine de traiter avec un langage scripté.

10
Andrew Ensley

Un peu tard pour la fête, mais certains aspects non encore mentionnés des fonctions, anonymes ou non ...

Il n'est pas facile de faire référence aux func anon dans les conversations humanoïdes sur le code, au sein d'une équipe. Par exemple, "Joe, pourriez-vous expliquer ce que fait l'algorithme dans cette fonction. ... Laquelle? La 17e fonction anonyme dans la fonction fooApp. ... Non, pas celle-là! La 17e!"

Les fonctions anon sont également anonymes pour le débogueur. (duh!) Par conséquent, la trace de la pile du débogueur n'affiche généralement qu'un point d'interrogation ou similaire, ce qui la rend moins utile lorsque vous avez défini plusieurs points d'arrêt. Vous atteignez le point d'arrêt, mais vous vous trouvez à faire défiler la fenêtre de débogage vers le haut/bas pour savoir où diable vous êtes dans votre programme, parce que la fonction de point d'interrogation ne le fait tout simplement pas!

Les inquiétudes concernant la pollution de l'espace de noms global sont valides, mais facilement résolues en nommant vos fonctions en tant que nœuds au sein de votre propre objet racine, comme "myFooApp.happyFunc = function (...) {...};".

Les fonctions disponibles dans l'espace de noms global, ou en tant que nœuds dans votre objet racine comme ci-dessus, peuvent être appelées directement depuis le débogueur, pendant le développement et le débogage. Par exemple, sur la ligne de commande de la console, faites "myFooApp.happyFunc (42)". Il s'agit d'une capacité extrêmement puissante qui n'existe pas (nativement) dans les langages de programmation compilés. Essayez cela avec une fonction anon.

Les fonctions anon peuvent être rendues plus lisibles en les affectant à un var, puis en passant le var comme rappel (au lieu de l'inlining). Par exemple: var funky = function (...) {...}; jQuery ('# otis'). click (funky);

En utilisant l'approche ci-dessus, vous pouvez potentiellement regrouper plusieurs fonctions anon en haut de la fonction parentale, puis en dessous, la masse des instructions séquentielles devient beaucoup plus serrée et plus facile à lire.

3
RickS

Eh bien, juste pour être clair pour mes arguments, ce sont toutes les fonctions/expressions de fonction anonymes dans mon livre:

var x = function(){ alert('hi'); },

indexOfHandyMethods = {
   hi: function(){ alert('hi'); },
   high: function(){
       buyPotatoChips();
       playBobMarley();
   }
};

someObject.someEventListenerHandlerAssigner( function(e){
    if(e.doIt === true){ doStuff(e.someId); }
} );

(function namedButAnon(){ alert('name visible internally only'); })()

Avantages:

  • Il peut réduire un peu la cruauté, en particulier dans les fonctions récursives (où vous pourriez (devrait en fait, car arguments.callee est déconseillé) utiliser une référence nommée par le dernier exemple en interne), et indique clairement que la fonction ne se déclenche que dans celui-ci endroit.

  • Gain de lisibilité du code: dans l'exemple du littéral objet avec des fonctions anon assignées comme méthodes, il serait idiot d'ajouter plus d'endroits à Hunt et à picorer la logique dans votre code lorsque le but de ce littéral objet est de replacer certaines fonctionnalités connexes dans le même endroit commodément référencé. Cependant, lorsque je déclare des méthodes publiques dans un constructeur, j'ai tendance à définir des fonctions étiquetées en ligne, puis à les affecter comme références de this.sameFuncName. Cela me permet d'utiliser les mêmes méthodes en interne sans le "ceci". dépouillé et fait de l'ordre de définition une préoccupation quand ils s'appellent.

  • Utile pour éviter la pollution inutile des espaces de noms mondiaux - les espaces de noms internes, cependant, ne devraient jamais être aussi largement remplis ou manipulés par plusieurs équipes simultanément, de sorte que cet argument me semble un peu idiot.

  • Je suis d'accord avec les rappels en ligne lors de la définition de gestionnaires d'événements courts. C'est idiot de devoir rechercher une fonction de 1 à 5 lignes, d'autant plus qu'avec JS et la fonction de levage, les définitions peuvent se retrouver n'importe où, pas même dans le même fichier. Cela pourrait arriver par accident sans rien casser et non, vous n'avez pas toujours le contrôle de ce genre de choses. Les événements entraînent toujours le déclenchement d'une fonction de rappel. Il n'y a aucune raison d'ajouter plus de liens à la chaîne de noms que vous devez parcourir uniquement pour effectuer une rétro-ingénierie de simples gestionnaires d'événements dans une grande base de code et le problème de trace de pile peut être résolu en résumant les déclencheurs d'événements eux-mêmes dans des méthodes qui consignent des informations utiles lors du débogage. Le mode est activé et déclenche les déclencheurs. En fait, je commence à construire des interfaces entières de cette façon.

  • Utile lorsque vous VOULEZ que l'ordre de définition des fonctions compte. Parfois, vous voulez être certain qu'une fonction par défaut est ce que vous pensez qu'elle est jusqu'à un certain point du code où vous pouvez la redéfinir. Ou vous voulez que la rupture soit plus évidente lorsque les dépendances sont mélangées.

Les inconvénients:

  • Les fonctions anon ne peuvent pas profiter du levage de fonction. C'est une différence majeure. J'ai tendance à tirer le meilleur parti du hissage pour définir mes propres fonctions et constructeurs d'objets explicitement nommés vers le bas et accéder à la définition d'objet et au type de boucle principale tout en haut. Je trouve que cela rend le code plus facile à lire lorsque vous nommez bien vos vars et que vous obtenez une vue d'ensemble de ce qui se passe avant ctrl-Fing pour les détails uniquement quand ils vous importent. Le levage peut également être un énorme avantage dans les interfaces fortement événementielles où imposer un ordre strict de ce qui est disponible peut vous mordre le cul. Le levage a ses propres mises en garde (comme le potentiel de référence circulaire), mais c'est un outil très utile pour organiser et rendre le code lisible lorsqu'il est utilisé correctement.

  • Lisibilité/débogage. Absolument, ils sont parfois trop utilisés et cela peut compliquer le débogage et la lisibilité du code. Les bases de code qui s'appuient fortement sur JQ, par exemple, peuvent être un PITA sérieux à lire et à déboguer si vous n'encapsulez pas les arguments presque inévitables et lourdement surchargés de la soupe $ de manière sensée. La méthode de survol de JQuery, par exemple, est un exemple classique de surutilisation des fonctions anon lorsque vous y déposez deux fonctions anon, car il est facile pour un débutant de supposer qu'il s'agit d'une méthode d'affectation d'écouteur d'événement standard plutôt que d'une méthode surchargée à affecter gestionnaires pour un ou deux événements. $(this).hover(onMouseOver, onMouseOut) est beaucoup plus clair que deux fonctions anon.

1
Erik Reppen

Il est plus lisible en utilisant des fonctions nommées et ils sont également capables de s'auto-référencer comme dans l'exemple ci-dessous.

(function recursion(iteration){
    if (iteration > 0) {
      console.log(iteration);
      recursion(--iteration);
    } else {
      console.log('done');
    }
})(20);

console.log('recursion defined? ' + (typeof recursion === 'function'));

http://jsfiddle.net/Yq2WD/

C'est bien quand vous voulez avoir une fonction immédiatement invoquée qui se référence elle-même mais qui ne s'ajoute pas à l'espace de noms global. C'est toujours lisible mais pas polluant. Prenez votre gâteau et mangez-le.

Salut, mon nom est Jason OR salut, mon nom est ???? vous choisissez.

1
Jason Sebring