Comment s'appelle ce modèle JavaScript et pourquoi est-il utilisé?


100

J'étudie THREE.js et j'ai remarqué un modèle où les fonctions sont définies comme suit:

var foo = ( function () {
    var bar = new Bar();

    return function ( ) {
        //actual logic using bar from above.
        //return result;
    };
}());

(Exemple voir la méthode raycast ici ).

La variation normale d'une telle méthode ressemblerait à ceci:

var foo = function () {
    var bar = new Bar();

    //actual logic.
    //return result;
};

En comparant la première version à la variante normale , la première semble différer en cela:

  1. Il affecte le résultat d'une fonction auto-exécutable.
  2. Il définit une variable locale au sein de cette fonction.
  3. Il renvoie la fonction réelle contenant la logique qui utilise la variable locale.

Ainsi, la principale différence est que dans la première variante, la barre n'est affectée qu'une seule fois, à l'initialisation, tandis que la seconde variante crée cette variable temporaire à chaque fois qu'elle est appelée.

Ma meilleure estimation sur la raison pour laquelle cela est utilisé est qu'il limite le nombre d'instances pour bar (il n'y en aura qu'une) et économise ainsi la gestion de la mémoire.

Mes questions:

  1. Cette hypothèse est-elle correcte?
  2. Y a-t-il un nom pour ce modèle?
  3. Pourquoi est-ce utilisé?

1
@ChrisHayes assez juste. Je l'ai marqué comme THREE.js parce que je pensais que les contributeurs THREE.js sont les plus qualifiés pour répondre à cela mais oui, c'est une question JS générique.
Patrick Klug

2
Je crois que cela s'appelle des fermetures. Vous pouvez lire à leur sujet.
StackFlowed le

1
Si c'est le seul endroit où une barre est instanciée, il s'agit d'un motif singleton .
Paul

8
Pas nécessairement pour économiser de la mémoire, mais il peut garder l'état à travers les invocations
Juan Mendes

2
@wrongAnswer: pas exactement. ici la fonction anonyme (qui serait la fermeture) est exécutée immédiatement.
njzk2

Réponses:


100

Vos hypothèses sont presque correctes. Passons en revue ces premiers.

  1. Il affecte le retour d'une fonction auto-exécutable

Cela s'appelle une expression de fonction immédiatement invoquée ou IIFE

  1. Il définit une variable locale au sein de cette fonction

C'est la manière d'avoir des champs d'objet privé dans JavaScript car il ne fournit pas le privatemot - clé ou la fonctionnalité autrement.

  1. Il renvoie la fonction réelle contenant la logique qui utilise la variable locale.

Là encore, le point principal est que cette variable locale est privée .

Y a-t-il un nom pour ce modèle?

AFAIK vous pouvez appeler ce modèle de modèle de module . Citant:

Le modèle de module encapsule la «confidentialité», l'état et l'organisation à l'aide de fermetures. Il fournit un moyen d'envelopper un mélange de méthodes et de variables publiques et privées, en protégeant les pièces contre les fuites dans la portée globale et la collision accidentelle avec l'interface d'un autre développeur. Avec ce modèle, seule une API publique est renvoyée, gardant tout le reste de la fermeture privé.

En comparant ces deux exemples, mes meilleures suppositions sur les raisons pour lesquelles le premier est utilisé sont:

  1. Il met en œuvre le modèle de conception Singleton.
  2. On peut contrôler la façon dont un objet d'un type spécifique peut être créé en utilisant le premier exemple. Une correspondance étroite avec ce point peut être des méthodes de fabrique statiques comme décrit dans Java efficace.
  3. C'est efficace si vous avez besoin du même état d'objet à chaque fois.

Mais si vous avez juste besoin de l'objet vanilla à chaque fois, alors ce modèle n'ajoutera probablement aucune valeur.


1
+1 pour identifier correctement le motif du livre d'Addy Osmani. Vous avez raison dans votre dénomination - c'est en effet le modèle de module - le modèle de module révélateur d'ailleurs.
Benjamin Gruenbaum

4
Je suis d'accord avec votre réponse, sauf la partie «pas de variables privées prêtes à l'emploi». Toutes les variables JS ont une portée lexicale «prête à l'emploi», ce qui est un mécanisme plus puissant / général que les variables «privées» (par exemple, comme trouvé en Java); par conséquent, JS prend en charge les variables "privées" comme cas particulier de la manière dont il gère toutes les variables.
Warbo le

Je pense que par "variables privées", vous vouliez dire "champ d'objet" privé
Ian Ringrose


4
@LukaHorvat: en fait, javascript n'est pas plus "puissant" que les autres langages (je préfère le terme expressif). En fait, c'est moins expressif, car le seul moyen de protéger vos variables est de les enfermer dans la fonction pour éviter toute absurdité de réutilisation de variables. Le modèle de module est une condition sine qua non pour créer un bon code javascript, mais ce n'est pas une caractéristique du langage, c'est plutôt une triste solution de contournement pour éviter d'être mordu par les faiblesses du langage.
Falanwe

11

Il limite les coûts d'initialisation des objets et garantit en outre que toutes les invocations de fonctions utilisent le même objet. Cela permet, par exemple, de stocker l'état dans l'objet pour de futures invocations.

Bien qu'il soit possible que cela limite l'utilisation de la mémoire, généralement le GC collectera les objets inutilisés de toute façon, donc ce modèle n'aidera probablement pas beaucoup.

Ce modèle est une forme spécifique de fermeture .


1
Dans JS, il est généralement désigné par `` module ''
Lesha Ogonkov

2
Je n'appellerais pas cela «une forme spécifique de fermeture» en soi. C'est un modèle qui utilise une fermeture. Le nom du motif est toujours à gagner.
Chris Hayes

4
A-t-il vraiment besoin d'un nom? Tout doit-il être un modèle? Avons-nous vraiment besoin d'une taxonomie sans fin de variantes de "modèle de module"? Ne peut-il pas être simplement "un IIFE avec des variables locales qui renvoie une fonction?"
Dagg Nabbit le

3
@DaggNabbit Quand la question est "comment s'appelle ce modèle"? Oui, il a besoin d'un nom ou bien d'un argument convaincant qu'il n'en a pas. En outre, les modèles existent pour une raison. Je ne comprends pas pourquoi vous vous insultez contre eux ici.
Chris Hayes

4
@ChrisHayes s'il a besoin d'un nom, pourquoi auriez-vous besoin de faire valoir qu'il n'en a pas? Cela n'a aucun sens. S'il en a besoin, il ne doit pas en avoir. Je n'ai aucun problème avec les modèles, mais je ne pense pas qu'il soit nécessaire de classer chaque idiome simple comme un modèle. Cela conduit à réfléchir de manière limitée ("Est-ce un modèle de module? Est-ce que j'utilise correctement le modèle de module?" Vs "J'ai un IIFE avec des variables locales qui renvoie une fonction, est-ce que cette conception fonctionne pour moi?")
Dagg Nabbit

8

Je ne suis pas sûr que ce modèle ait un nom plus correct, mais cela ressemble à un module pour moi, et la raison pour laquelle il est utilisé est à la fois d'encapsuler et de maintenir l'état.

La fermeture (identifiée par une fonction dans une fonction) garantit que la fonction interne a accès aux variables de la fonction externe.

Dans l'exemple que vous avez donné, la fonction interne est renvoyée (et affectée à foo) en exécutant la fonction externe, ce qui signifie qu'elle tmpObjectcontinue à vivre dans la fermeture et que plusieurs appels à la fonction interne foo()opèrent sur la même instance de tmpObject.


5

La principale différence entre votre code et le code Three.js est que dans le code Three.js, la variable tmpObjectn'est initialisée qu'une seule fois, puis partagée par chaque appel de la fonction retournée.

Cela serait utile pour conserver un état entre les appels, de la même manière que les staticvariables sont utilisées dans les langages de type C.

tmpObject est une variable privée uniquement visible par la fonction interne.

Il modifie l'utilisation de la mémoire, mais il n'est pas conçu pour économiser de la mémoire.


5

J'aimerais contribuer à ce sujet intéressant en étendant au concept du modèle de module révélateur, qui garantit que toutes les méthodes et variables restent privées jusqu'à ce qu'elles soient explicitement exposées.

entrez la description de l'image ici

Dans ce dernier cas, la méthode d'addition serait appelée comme Calculator.add ();


0

Dans l'exemple fourni, le premier extrait de code utilisera la même instance de tmpObject pour chaque appel à la fonction foo (), où comme dans le deuxième extrait de code, tmpObject sera une nouvelle instance à chaque fois.

L'une des raisons pour lesquelles le premier extrait de code a pu être utilisé est que la variable tmpObject peut être partagée entre les appels à foo (), sans que sa valeur ne soit divulguée dans la portée dans laquelle foo () est déclarée.

La version de fonction non exécutée immédiatement du premier extrait de code ressemblerait en fait à ceci:

var tmpObject = new Bar();

function foo(){
    // Use tmpObject.
}

Notez cependant que cette version a tmpObject dans la même portée que foo (), donc il pourrait être manipulé plus tard.

Une meilleure façon d'obtenir la même fonctionnalité serait d'utiliser un module séparé:

Module 'foo.js':

var tmpObject = new Bar();

module.exports = function foo(){
    // Use tmpObject.
};

Module 2:

var foo = require('./foo');

Une comparaison entre les performances d'un IEF et d'une fonction de créateur foo nommée: http://jsperf.com/ief-vs-named-function


3
Votre «meilleur» exemple ne fonctionne que dans NodeJS et vous n'avez pas expliqué comment il est meilleur.
Benjamin Gruenbaum

Faire un module séparé n'est pas «mieux», c'est juste différent. En particulier, c'est un moyen de réduire les fonctions d'ordre supérieur en objets de premier ordre. Le code de premier ordre a tendance à être plus facile à parcourir, mais il est généralement plus détaillé et nous oblige à réifier les résultats intermédiaires.
Warbo le

Les modules @BenjaminGruenbaum ne sont pas seulement dans Node, il existe de nombreuses solutions de modules côté client, par exemple, browserify. Je considère que la solution du module est "meilleure" car elle est plus lisible, plus facile à déboguer et est plus explicite sur ce qui est dans la portée et où.
Kory Nunn
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.