102 votes

Définissez une fonction à l'intérieur d'une autre fonction en JavaScript

function foo(a) {
    if (/* Certaines conditions */) {
        // effectuer la tâche 1
        // effectuer la tâche 3
    }
    else {
        // effectuer la tâche 2
        // effectuer la tâche 3
    }
}

J'ai une fonction dont la structure est similaire à celle ci-dessus. Je veux abstraire tâche 3 dans une fonction, bar(), mais je souhaite limiter l'accès de cette fonction uniquement dans la portée de foo(a).

Pour réaliser ce que je veux, est-il correct de changer pour ce qui suit ?

function foo(a) {
    function bar() {
        // Effectuer la tâche 3
    }

    if (/* Certaines conditions */) {
        // Effectuer la tâche 1
        bar();
    }
    else {
        // Effectuer la tâche 2
        bar();
    }
}

Si ce qui précède est correct, bar() est-elle redéfinie chaque fois que foo(a) est appelée ? (Je m'inquiète de la perte de ressources CPU ici.)

146voto

T.J. Crowder Points 285826

Oui, ce que vous avez là est correct. Quelques notes :

  • bar est créé à chaque appel de la fonction foo, mais :
    • Sur les navigateurs modernes, c'est un processus très rapide. (Certains moteurs ne compilent peut-être le code qu'une seule fois, pour ensuite réutiliser ce code avec un contexte différent à chaque fois; le moteur V8 de Google [dans Chrome et ailleurs] le fait dans la plupart des cas.)
    • Et en fonction de ce que fait bar, certains moteurs peuvent déterminer qu'ils peuvent le "plonger" directement, éliminant ainsi complètement l'appel de fonction. V8 le fait, et je suis sûr que ce n'est pas le seul moteur à le faire. Naturellement, ils ne peuvent le faire que si cela ne modifie pas le comportement du code.
  • L'impact sur les performances, le cas échéant, de la création de bar à chaque fois variera largement d'un moteur JavaScript à l'autre. Si bar est trivial, cela variera de non détectable à assez faible. Si vous n'appelez pas foo des milliers de fois d'affilée (par exemple, à partir d'un gestionnaire mousemove), je ne m'en préoccuperais pas. Même si vous le faites, je ne m'inquiéterais que si je voyais un problème sur des moteurs plus lents. Voici un cas de test impliquant des opérations de DOM, qui suggère qu'il y a un impact, mais un impact trivial (probablement noyé par le truc du DOM). Voici un cas de test effectuant une pure computation qui montre un impact beaucoup plus important, mais honnêtement, nous parlons même de différence de _micro_secondes car même une augmentation de 92% sur quelque chose qui prend des _micro_secondes pour se produire est encore très, très rapide. Jusqu'à ce que vous observiez un impact réel, ce n'est pas quelque chose sur quoi s'inquiéter.
  • bar sera accessible uniquement depuis l'intérieur de la fonction, et il a accès à toutes les variables et arguments pour cet appel de fonction. Cela en fait un motif très pratique.
  • Remarquez que parce que vous avez utilisé une déclaration de fonction, peu importe où vous avez placé la déclaration (en haut, en bas ou au milieu — tant que c'est au niveau supérieur de la fonction, pas à l'intérieur d'une instruction de contrôle de flux, ce qui est une erreur de syntaxe), elle est définie avant l'exécution de la première ligne de code pas à pas.

16voto

Michiel Borkent Points 11503

C'est à cela que servent les fermetures.

var foo = (function () {
  function bar() {
    // effectuer la tâche 3
  };

  function innerfoo (a) { 
    if (/* certaine condition */ ) {
      // effectuer la tâche 1
      bar();
    }
    else {
      // effectuer la tâche 2
      bar();
    }
  }
  return innerfoo;
})();

Innerfoo (une fermeture) contient une référence à bar et seule une référence à innerfoo est renvoyée à partir d'une fonction anonyme qui n'est appelée qu'une seule fois pour créer la fermeture.

De cette façon, bar n'est pas accessible de l'extérieur.

10voto

robrich Points 5284
var foo = (function () {
    var bar = function () {
        // effectuer la tâche 3
    }
    return function (a) {

        if (/* certaine condition */) {
            // effectuer la tâche 1
            bar();
        }
        else {
            // effectuer la tâche 2
            bar();
        }
    };
}());

La fermeture garde la portée de bar() contenue, en retournant la nouvelle fonction de la fonction anonyme auto-exécutante, cela donne une portée plus visible à foo(). La fonction anonyme auto-exécutante est exécutée une seule fois, donc il n'y a qu'une seule instance de bar(), et chaque exécution de foo() l'utilisera.

9voto

Guffa Points 308133

Oui, ça fonctionne bien.

La fonction interne n'est pas recréée à chaque fois que vous entrez dans la fonction externe, mais elle est réassignée.

Si vous testez ce code :

function test() {

    function demo() { alert('1'); }

    demo();
    demo = function() { alert('2'); };
    demo();

}

test();
test();

cela affichera 1, 2, 1, 2, pas 1, 2, 2, 2.

0voto

Michael Liquori Points 269

J'ai créé un jsperf pour tester les fonctions imbriquées par rapport aux fonctions non imbriquées et les expressions de fonction par rapport aux déclarations de fonction, et j'ai été surpris de voir que les cas de test imbriqués étaient 20 fois plus rapides que les non imbriqués. (Je m'attendais à l'inverse ou à des différences négligeables).

https://jsperf.com/nested-functions-vs-not-nested-2/1

Ceci est sur Chrome 76, macOS.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X