11548 sujets

JavaScript, DOM et API Web HTML5

Bonjour,

J'ai fait quelques recherches sur les syntaxes de modules en JavaScript. Je cherchais un moyen d'encapsuler du code JS de sorte à ne pas polluer l'espace de nom plus que nécessaire. J'aimerais avoir votre avis sur les structures qui vous semblent les plus utiles et sur les conventions syntaxiques que vous utilisez.

Parmi mes lectures:
- JavaScript : organiser son code en modules (Julien Royer)
- Again with the Module Pattern – reveal something to the world (Chris Heilmann)
- JavaScript Module Pattern: In-Depth (Ben Cherry)
- Why I don't love JavaScript's Module Pattern (Jonathan Snook)

Mes questions (notez que j'utilise des termes pas toujours corrects Smiley cligne ):

Module anonyme
Deux syntaxes équivalentes (la différence est dans le placement de la parenthèse à la fin). Laquelle est à privilégier selon vous?
(function(){ 
    // 1
})();

(function(){ 
    // 2
}());


Module nommé: encore des parenthèses?
Les deux syntaxe marchent et semblent équivalentes. Est-ce bien le cas? Si oui, laquelle est plus utilisée, par convention? J'ai une petite préférence pour la première syntaxe, car si on peut limiter l'orgie de parenthèses et accolades habituelle en JavaScript, je suis pas contre. Smiley ohwell
var MODULE1 = (function(){ 
    // ...
}());

var MODULE2 = function(){ 
    // ...
}();


Module anonyme, pas possible sans les parenthèses?
Ça marche pour un module nommé (deuxième syntaxe ci-dessus), mais pas pour un anonyme? Comment se fait-ce? Une décision arbitraire d'ECMAScript?
/* Ok ça marche */
var MODULE = function(){ 
    // 1
}();

/* Syntax error, méheucépajuste! */
function(){ 
    // 2
}();


Module nommé, exposer des propriétés et méthodes
Après mes lectures, je suis plutôt partisan de la syntaxe du "revealing module pattern". On travaille tranquillement avec une syntaxe «classique» pour définir des variables et fonctions, et ensuite en liste celles qui doivent être visible de l'extérieur. Votre avis?
L'exemple qui va bien:
var MODULE = function(){
  var machin = 1, truc = 42;
  function bidule(){
    // ...
  };
  function chose(){
    // ...
  }
  return {truc:truc, chose:chose}
}();

Modifié par Florent V. (16 Jul 2010 - 14:51)
Salut,

À la base du problème on trouve la différence entre :
- une expression : pour simplifier, c'est une valeur (ex: 7, 3+4, true, 'toto', {'a':1, 'b':2, 'c':3}, new MonObjet(), etc.)
- et une déclaration : c'est le fait d'assigner une valeur à une variable. (ex: var toto = '3')

Tu obtiendras un traitement (parfois) différent selon que ta fonction est considérée par l'interpréteur js comme l'une ou l'autre. Explication avec tes exemples, dans le désordre :
function(){  
    // 2 
}();
Par défaut (disons...), le mot function introduit une déclaration de fonction. Donc parsage en tout premier, avant la moindre exécution de code, et enregistrement dans une table de références pour pouvoir être appelée plus tard. Et pour cela, il lui faut bien sûr un nom (sinon comment l'appeler ?...). Donc erreur de syntaxe dans cet exemple.

(function(){  
    // 1 
})();
Les parenthèses ici servent à forcer l'interprétation d'une instruction en tant qu'expression. Une expression peut être tout et n'importe quoi, y compris une fonction (auquel cas elle est exécutable, bien sûr). C'est ce qui se passe ici et tu n'as donc plus d'erreur. Pour l'interpréteur, c'est un peu comme si tu avais écrit :
var toto = function(){...};
toto();
...sauf que là, la fonction étant nommée, elle aurait été enregistrée pour utilisation ultérieure (cf. point précédent).

(function(){  
    // 2 
}());
Ici c'est un peu la même chose qu'au-dessus, sauf que c'est le tout qui est considéré comme une expression (du fait des parenthèses). Le résultat est le même mais je préfère la version précédente qui, à mon sens, indique de manière plus explicite ce que je veux faire de la fonction.

var MODULE = function(){  
    // 1 
}(); 
Par "spécification", tout ce qui se trouve à droite du signe égal ne peut être qu'expression, donc là aussi, pas de risque de déclencher une erreur de déclaration. Même chose que ci-dessus, tu peux aussi choisir de bien marquer, explicitement, que ta fonction doit être traitée comme une expression, et donc préférer cette forme :
var MODULE = (function(){  
    // 1 
})(); 
Et loin de participer à l'orgie de parenthèses, cette paire-là contribue souvent, de plus, à faciliter la lecture.
Modifié par marcv (16 Jul 2010 - 18:01)
Merci monsieur Marc V. pour cette réponse fort détaillée. Smiley jap

Je pense avoir compris la différence expression/déclaration, même si quand on arrive du côté de function ça fait un peu chauffer le cerveau.

OK pour le gain de lisibilité des parenthèses en plus pour les modules nommés. La cohérence avec la syntaxe des modules anonymes est un autre argument qui va bien. J'achète.

(function(){
  // …
})() // Parenthèses «dehors»

(function(){
  // …
}()) // Parenthèses «dedans»

Pour le coup des () dedans ou dehors, j'ai eu deux sons de cloche:
- tu préfères la version dehors, «qui indique de manière plus explicite ce que je veux faire de la fonction» (et ce qu'on veut en faire c'est l'exécuter, donc?);
- pour d'autres, la version dedans «clarifie le fait que ton code retourne une valeur et non la fonction elle même».
On fait un sondage? Smiley lol

Enfin, s'il y a des critiques du «revealing module pattern», et des syntaxes qui vous privilégiez, ça m'intéresse.
Florent V. a écrit :
«qui indique de manière plus explicite ce que je veux faire de la fonction» (et ce qu'on veut en faire c'est l'exécuter, donc?);
Hmm non, je me suis mal exprimé, ce que je voulais dire c'est plutôt «qui indique de manière plus explicite que ma fonction doit être traitée comme une expression (et non pas comme une déclaration)».

Pour le tweet de piouPiouM que tu cites au sujet de la solution "parenthèses dedans", je ne comprends pas vraiment son explication, mais de toutes façons je crois que ce point là n'a que très peu (voire pas) d'impact sur l'exécution du code et j'aurais donc tendance à le voir simplement comme une préférence de style.
Dans tes deux premiers exemples, Module anonyme et Module nommé, les deux syntaxes sont équivalentes. Seul diffère la convention qui, dans sa seconde forme, aide à la compréhension. Elle précise que c'est le résultat de la fonction qui est retourné et non la fonction elle-même. Ce qui est surtout utile lorsque que tu assignes ta fonction auto-exécutée à une variable.

Dans ton troisième exemple, la seconde définition lève une exception parce que tu exprimes une fonction (Function expression) dans un contexte global. En effet, parce que ta fonction est définie au plus haut niveau de ton code (également valable dans le corps d'une autre fonction), le moteur ECMAScript cherche à analyser une déclaration de fonction (Function declaration). Voir la spécification sur la définition de fonctions.
Si tu souhaites utiliser la seconde syntaxe de l'exemple, donc effectuer une expression de fonction, tu as besoin de préciser un contexte. Cela se fait simplement en englobant ton expression de l'opérateur de groupement "(" et ")".

Note que l'exception sera également levée en présence d'une expression de fonction nommée (Named function expression) :

try {
  function foo () {
    return "bar";
  }();
} catch (err) {
  // SyntaxError
}


try {
  (function foo () {
    return "bar"
  }());
} catch (err) {
  // Nothing or prints "bar" to the console of Firebug
}


Bien que plus pratique pour effectuer les séances de debug, l'implémentation de la syntaxe Named function expression est bugguée dans JSCript (IE) et Safari 2.x.


Pour en revenir au sujet de ta question, j'opte pour la syntaxe module nommé, avec la fonction auto-exécutée englobée par des parenthèses : la lecture est plus simple, je sais que ma variable sera initialisée avec le résultat de la fonction anonyme de la partie droite et qu'elle ne l'exécutera pas à chaque appel.
marcv a écrit :
Hmm non, je me suis mal exprimé, ce que je voulais dire c'est plutôt «qui indique de manière plus explicite que ma fonction doit être traitée comme une expression (et non pas comme une déclaration)».

Pour le tweet de piouPiouM que tu cites au sujet de la solution "parenthèses dedans", je ne comprends pas vraiment son explication, mais de toutes façons je crois que ce point là n'a que très peu (voire pas) d'impact sur l'exécution du code et j'aurais donc tendance à le voir simplement comme une préférence de style.


Effectivement ça n'a absolument aucun impact, ce sont deux syntaxes strictement équivalentes.

Personnellement je préfère la syntaxe extérieur pour 3 raisons :
1. j'ai pris l'habitude de considérer les parenthèses comme un "opérateur d'exécution", hors un opérateur ne se mélange pas à son opérande
2. quand tu bosse cela te permet de désactiver très facilement des modules :

(function(){
// module qui provoque un conflit avec un autre bout de code
// on le désactive le temps de débugguer
})//();


3. c'est plus facile de voir des parenthèses manquantes quand elles ne sont pas imbriquées, et ça m'évite de réveiller de vieux cauchemards impliquant les infames syntaxes LISP/SCHEME et tous leurs dérivés.
Hello,

J'aime bien le "revealing module pattern" pour sa simplicité et parce qu'il définit clairement l'interface (partie publique) du module.

P.S. : il faudrait que je mette à jour l'article...
Modifié par Julien Royer (19 Jul 2010 - 11:45)
Merci à piouPiouM, MonsieurY et Julien.

Julien, si tu veux mettre l'article à jour, ben te gêne pas, tu as les accès qui vont bien. Smiley cligne
Modifié par Florent V. (19 Jul 2010 - 15:40)