web-dev-qa-db-fra.com

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

Je me demandais 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?

80
nickf

Le problème de performances réside ici dans le coût de création d'un nouvel objet fonction à chaque itération de la boucle et non dans 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 ). Par contre, ce qui suit semble plus rapide, car il attribue simplement la référence de fonction same 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, associez-lui uniquement des références aux éléments du tableau à l'intérieur de la boucle. Vous constaterez qu'il n'y a aucune différence de performance ou 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 résumé, l’utilisation de fonctions anonymes par rapport à des fonctions nommées n’entraîne aucun coût en termes de performances.

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 dernier est une affectation de variable à une fonction anonyme. Bien qu'ils puissent sembler avoir le même effet, JavaScript les traite légèrement de manière différente. Pour comprendre la différence, je recommande de lire « Ambiguïté de la déclaration de la fonction JavaScript ».

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

81
Atif Aziz

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 resultats:
Test 1: 142ms Test 2: 1983ms

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

21
nickf

En règle générale, évitez de mettre en œuvre le même code plusieurs fois. Au lieu de cela, vous devriez extraire du code commun dans une fonction et exécuter cette fonction (générale, bien testée, facile à modifier) ​​à partir de plusieurs emplacements.

Si (contrairement à ce que vous déduisez de votre question) vous déclarez la fonction interne une fois et utilisez ce code une fois (et n’avez rien d’autre dans votre programme), alors une fonction anonyme probablement (c'est une hypothèse pour les gens) est traitée de la même manière par le compilateur comme une fonction nommée normale.

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

2
Tom Leys

Nous pouvons avoir un impact sur les performances dans le fonctionnement des fonctions de déclaration. Voici un repère de déclaration de fonctions dans le contexte d'une autre fonction ou à l'extérieur:

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

Sous Chrome, l'opération est plus rapide si nous déclarons la fonction à l'extérieur, mais sous Firefox, c'est l'inverse.

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

1
Pablo Estornut

Je ne m'attendrais pas à beaucoup de différence, mais s'il en existe une, elle variera probablement selon le moteur de script ou le navigateur. 

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

1
Joe Skora

Les objets anonymes sont plus rapides que les objets nommés. Mais appeler plus de fonctions coûte plus cher, et à un point 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 surcharge légère mais non négligeable.

Mais à moins d'écrire des routines de cryptage/décryptage ou quelque chose d'aussi sensible aux performances, comme d'autres l'ont déjà noté, il est toujours préférable d'optimiser un code élégant, facile à lire, plutôt qu'un code rapide.

En supposant que vous écriviez du code bien structuré, les problèmes de rapidité devraient être de la responsabilité de ceux qui écrivent les interprètes/compilateurs.

1
pcorcoran

@ nickf

C'est un test plutôt fatal, vous comparez l'exécution et la compilation le temps qui va évidemment coûter cher 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 transmettrait son code de probation de cette manière.

L'affectation anonyme est une approche beaucoup plus réaliste, car vous utilisez en fait votre méthode document.onclick, qui ressemble plutôt à la suivante, qui privilégie en fait légèrement la méthode anon.

Utiliser un framework 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
annakata

@ nickf

(J'aurais aimé que le représentant puisse commenter, mais je viens juste de trouver ce site)

Mon point est qu'il y a une confusion ici entre les fonctions nommées/anonymes et le cas d'utilisation de + 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 si ce n’est pas le cas, le meilleur conseil est de ne pas faire de bêtises (dont le déplacement constant de bloc + la création d’objet de ce cas d’utilisation) en est une. Si vous n’êtes pas sûr, testez!

0
annakata

OUI! Les fonctions anonymes sont plus rapides que les fonctions habituelles. Peut-être que si la vitesse est de la plus haute importance… plus importante que la réutilisation de code, envisagez d'utiliser des fonctions anonymes.

Il y a un très bon article sur l'optimisation des fonctions javascript et anonymes ici:

http://dev.opera.com/articles/view/efficient-javascript/?page=2

0
Christopher Tokar

une référence sera presque toujours plus lente que la chose à laquelle elle fait référence. Pensez-y de cette façon. Supposons que vous vouliez 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 réalise que c'est une façon très simpliste de voir les choses, mais c'est illustratif, n'est-ce pas? Utilisez une référence uniquement si elle doit être utilisée plusieurs fois - par exemple, lequel de ces exemples a 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);

La seconde est une meilleure pratique, même si elle contient plus de lignes. J'espère que tout cela est utile. (et la syntaxe de jquery n'a dérangé personne)

0
matt lohkamp

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

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

est tout simplement oui. Mais comme le montre sa performance JS, il ne ralentit pas d’un facteur 1 000, ce qui montre qu’il devient de plus en plus rapide au fil du temps.

La question la plus intéressante pour moi est:

Comment un create + run répété se compare-t-il pour créer une fois + run répété.

Si une fonction effectue un calcul complexe, le temps nécessaire pour créer l'objet fonction est probablement négligeable. Mais qu'en est-il de la surcharge de create dans les cas où run 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);
}

Cette JS Perf montre que la création de 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 création répétée de la fonction ne représente qu'une surcharge de quelques pour cent.

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

0
bluenote10

Ce qui va certainement rendre votre boucle plus rapide sur une variété de navigateurs, en particulier les navigateurs IE, fonctionne comme suit:

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

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

0
Sarhanis