L'utilisation de fonctions anonymes affecte-t-elle les performances?


89

Je me suis demandé s'il y avait une différence de performances entre l'utilisation de fonctions nommées et de fonctions anonymes en Javascript?

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = function() {
        // do something
    };
}

contre

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

Le premier est plus ordonné car il n'encombre pas votre code avec des fonctions rarement utilisées, mais est-il important que vous re-déclariez cette fonction plusieurs fois?


Je sais que ce n'est pas dans la question, mais en ce qui concerne la propreté / la lisibilité du code, je pense que la «bonne façon» est quelque part au milieu. Le «fouillis» de fonctions de premier niveau rarement utilisées est ennuyeux, mais il en est de même pour le code fortement imbriqué qui dépend beaucoup des fonctions anonymes qui sont déclarées en ligne avec leur invocation (pensez à l'enfer de callback node.js). Le premier et le second peuvent rendre le traçage de débogage / exécution difficile.
Zac B

Les tests de performances ci-dessous exécutent la fonction pendant des milliers d'itérations. Même si vous voyez une différence substantielle, la majorité des cas d'utilisation ne le feront pas dans les itérations de cet ordre. Par conséquent, il est préférable de choisir ce qui convient à vos besoins et d'ignorer les performances pour ce cas particulier.
utilisateur

@nickf bien sûr, sa question trop ancienne, mais voir la nouvelle réponse mise à jour
Chandan Pasunoori

Réponses:


89

Le problème de performances ici est le coût de création d'un nouvel objet fonction à chaque itération de la boucle et non le fait que vous utilisez une fonction anonyme:

for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = function() {
        // do something    
    };
}

Vous créez un millier d'objets de fonction distincts même s'ils ont le même corps de code et aucune liaison avec la portée lexicale ( fermeture ). Ce qui suit semble plus rapide, en revanche, car il affecte simplement la même référence de fonction aux éléments du tableau tout au long de la boucle:

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

Si vous deviez créer la fonction anonyme avant d'entrer dans la boucle, puis attribuez-lui des références uniquement aux éléments du tableau à l'intérieur de la boucle, vous constaterez qu'il n'y a aucune différence de performance ou de sémantique par rapport à la version de la fonction nommée:

var handler = function() {
    // do something    
};
for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = handler;
}

En bref, il n'y a aucun coût de performance observable à l'utilisation de fonctions anonymes sur des fonctions nommées.

En passant, il peut sembler d'en haut qu'il n'y a pas de différence entre:

function myEventHandler() { /* ... */ }

et:

var myEventHandler = function() { /* ... */ }

Le premier est une déclaration de fonction tandis que le second est une affectation de variable à une fonction anonyme. Bien qu'ils semblent avoir le même effet, JavaScript les traite légèrement différemment. Pour comprendre la différence, je vous recommande de lire « ambiguïté de déclaration de fonction JavaScript ».

Le temps d'exécution réel de toute approche sera en grande partie dicté par l'implémentation du compilateur et du runtime par le navigateur. Pour une comparaison complète des performances des navigateurs modernes, visitez le site JS Perf


Vous avez oublié les parenthèses avant le corps de la fonction. Je viens de le tester, ils sont obligatoires.
Chinoto Vokro

il semble que les résultats du benchmark soient très dépendants du moteur js!
aleclofabbro

3
N'y a-t-il pas une faille dans l'exemple JS Perf: le cas 1 définit uniquement la fonction, alors que les cas 2 et 3 semblent appeler accidentellement la fonction.
bluenote10

Donc, en utilisant ce raisonnement, cela signifie-t-il que lors du développement node.jsd'applications Web, il est préférable de créer les fonctions en dehors du flux de requêtes et de les transmettre en tant que rappels, que de créer des rappels anonymes?
Xavier T Mukodi le

23

Voici mon code de test:

var dummyVar;
function test1() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = myFunc;
    }
}

function test2() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = function() {
            var x = 0;
            x++;
        };
    }
}

function myFunc() {
    var x = 0;
    x++;
}

document.onclick = function() {
    var start = new Date();
    test1();
    var mid = new Date();
    test2();
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "\n Test 2: " + (end - mid));
}

Les résultats:
Test 1: 142ms Test 2: 1983ms

Il semble que le moteur JS ne reconnaît pas qu'il s'agit de la même fonction dans Test2 et la compile à chaque fois.


3
Dans quel navigateur ce test a-t-il été effectué?
andynil

5
Temps pour moi sur Chrome 23: (2ms / 17ms), IE9: (20ms / 83ms), FF 17: (2ms / 96ms)
Davy8

Votre réponse mérite plus de poids. Mon temps sur Intel i5 4570S: Chrome 41 (1/9), IE11 (1/25), FF36 (1/14). Il est clair que la fonction anonyme dans une boucle fonctionne moins bien.
ThisClark

3
Ce test n'est pas aussi utile qu'il n'y paraît. Dans aucun des deux exemples, la fonction intérieure n'est réellement exécutée. En fait, tout ce test montre que créer une fonction 10 000 000 fois est plus rapide que créer une fonction une fois.
Nucleon

2

En tant que principe de conception général, vous devez éviter d'implémenter le même code plusieurs fois. Au lieu de cela, vous devez extraire du code commun dans une fonction et exécuter cette fonction (générale, bien testée, facile à modifier) ​​à partir de plusieurs endroits.

Si (contrairement à ce que vous déduisez de votre question) vous déclarez la fonction interne une fois et en utilisant ce code une fois (et que vous avez rien d' autre identique dans votre programme) , puis une fonction anonomous probablement (thats un gens guess) obtient traité de la même manière par le compilateur en tant que fonction nommée normale.

C'est une fonctionnalité très utile dans des cas spécifiques, mais ne devrait pas être utilisée dans de nombreuses situations.


1

Je ne m'attendrais pas à beaucoup de différence, mais s'il y en a une, elle variera probablement en fonction du moteur de script ou du navigateur.

Si vous trouvez le code plus facile à utiliser, les performances ne sont pas un problème, sauf si vous prévoyez d'appeler la fonction des millions de fois.


1

Là où nous pouvons avoir un impact sur les performances, c'est dans l'opération de déclaration des fonctions. Voici un benchmark de la déclaration de fonctions dans le contexte d'une autre fonction ou à l'extérieur:

http://jsperf.com/function-context-benchmark

Dans Chrome, l'opération est plus rapide si nous déclarons la fonction à l'extérieur, mais dans Firefox, c'est le contraire.

Dans un autre exemple, nous voyons que si la fonction interne n'est pas une fonction pure, elle manquera également de performances dans Firefox: http://jsperf.com/function-context-benchmark-3


0

Ce qui rendra certainement votre boucle plus rapide sur une variété de navigateurs, en particulier les navigateurs IE, se déroule comme suit:

for (var i = 0, iLength = imgs.length; i < iLength; i++)
{
   // do something
}

Vous avez mis un 1000 arbitraire dans la condition de boucle, mais vous obtenez ma dérive si vous vouliez parcourir tous les éléments du tableau.


0

une référence sera presque toujours plus lente que la chose à laquelle elle fait référence. Pensez-y de cette façon - disons que vous voulez imprimer le résultat de l'ajout de 1 + 1. Ce qui est plus logique:

alert(1 + 1);

ou

a = 1;
b = 1;
alert(a + b);

Je me rends compte que c'est une façon vraiment simpliste de voir les choses, mais c'est illustratif, non? N'utilisez une référence que si elle doit être utilisée plusieurs fois - par exemple, lequel de ces exemples a le plus de sens:

$(a.button1).click(function(){alert('you clicked ' + this);});
$(a.button2).click(function(){alert('you clicked ' + this);});

ou

function buttonClickHandler(){alert('you clicked ' + this);}
$(a.button1).click(buttonClickHandler);
$(a.button2).click(buttonClickHandler);

Le second est une meilleure pratique, même s'il y a plus de lignes. J'espère que tout cela est utile. (et la syntaxe jquery n'a découragé personne)


0

@nickf

(j'aurais aimé avoir le représentant pour commenter, mais je viens de trouver ce site)

Mon point est qu'il existe ici une confusion entre les fonctions nommées / anonymes et le cas d'utilisation de l'exécution + compilation dans une itération. Comme je l'ai illustré, la différence entre anon + named est négligeable en soi - je dis que c'est le cas d'utilisation qui est défectueux.

Cela me semble évident, mais sinon je pense que le meilleur conseil est de "ne pas faire de choses stupides" (dont le décalage de bloc constant + création d'objet de ce cas d'utilisation en fait partie) et si vous n'êtes pas sûr, testez!



0

Les objets anonymes sont plus rapides que les objets nommés. Mais appeler plus de fonctions coûte plus cher, et dans une mesure qui éclipse les économies que vous pourriez obtenir en utilisant des fonctions anonymes. Chaque fonction appelée s'ajoute à la pile d'appels, ce qui introduit une petite quantité non négligeable de frais généraux.

Mais à moins que vous n'écriviez des routines de cryptage / décryptage ou quelque chose de similaire sensible aux performances, comme beaucoup d'autres l'ont noté, il est toujours préférable d'optimiser pour un code élégant et facile à lire sur un code rapide.

En supposant que vous écrivez un code bien conçu, les problèmes de vitesse devraient être la responsabilité de ceux qui écrivent les interprètes / compilateurs.


0

@nickf

C'est un test assez fatiguant cependant, vous comparez le temps d'exécution et de compilation là-bas qui va évidemment coûter la méthode 1 (compile N fois, selon le moteur JS) avec la méthode 2 (compile une fois). Je ne peux pas imaginer un développeur JS qui réussirait sa probation en écrivant du code de cette manière.

Une approche beaucoup plus réaliste est l'affectation anonyme, car en fait, vous utilisez pour votre méthode document.onclick ressemble plus à la suivante, qui en fait favorise légèrement la méthode anon.

En utilisant un cadre de test similaire au vôtre:


function test(m)
{
    for (var i = 0; i < 1000000; ++i) 
    {
        m();
    }
}

function named() {var x = 0; x++;}

var test1 = named;

var test2 = function() {var x = 0; x++;}

document.onclick = function() {
    var start = new Date();
    test(test1);
    var mid = new Date();
    test(test2);
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "ms\n Test 2: " + (end - mid) + "ms");
}

0

Comme indiqué dans les commentaires à @nickf answer: La réponse à

Crée une fonction une fois plus vite que de la créer un million de fois

est simplement oui. Mais comme le montre sa performance JS, elle n'est pas plus lente d'un facteur d'un million, ce qui montre qu'elle s'accélère réellement avec le temps.

La question la plus intéressante pour moi est:

Comment une création + exécution répétée se compare-t-elle à une exécution unique + une exécution répétée .

Si une fonction effectue un calcul complexe, le temps de création de l'objet fonction est très probablement négligeable. Mais qu'en est-il de la surcharge de création dans les cas où l' exécution est rapide? Par exemple:

// Variant 1: create once
function adder(a, b) {
  return a + b;
}
for (var i = 0; i < 100000; ++i) {
  var x = adder(412, 123);
}

// Variant 2: repeated creation via function statement
for (var i = 0; i < 100000; ++i) {
  function adder(a, b) {
    return a + b;
  }
  var x = adder(412, 123);
}

// Variant 3: repeated creation via function expression
for (var i = 0; i < 100000; ++i) {
  var x = (function(a, b) { return a + b; })(412, 123);
}

Ce JS Perf montre que créer la fonction une seule fois est plus rapide que prévu. Cependant, même avec une opération très rapide comme un simple ajout, la surcharge liée à la création répétée de la fonction n'est que de quelques pour cent.

La différence ne devient probablement significative que dans les cas où la création de l'objet fonction est complexe, tout en conservant un temps d'exécution négligeable, par exemple, si le corps de la fonction entière est enveloppé dans un fichier if (unlikelyCondition) { ... }.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.