Motif pour les modules CoffeeScript

Lors de l'examen du code source de CoffeeScript sur Github , j'ai remarqué que la plupart des modules, sinon tous, sont définis comme suit:

(function() { ... }).call(this); 

Ce modèle semble qu'il enveloppe le module entier dans une fonction anonyme et s'appelle lui-même.

Quels sont les avantages (et les inconvénients) de cette approche? Y a-t-il d'autres façons d'atteindre les mêmes objectifs?

La réponse de Harmen est assez bonne, mais permettez-moi d'expliquer un peu le but de ce compilateur CoffeeScript et pourquoi.

Lorsque vous compilez quelque chose avec coffee -c foo.coffee , vous obtiendrez toujours un foo.js qui ressemble à ceci:

 (function() { ... }).call(this); 

Pourquoi donc? Eh bien, supposons que vous mettiez une affectation comme

 x = 'stringy string' 

Dans foo.coffee . Quand il le voit, le compilateur demande: Est-ce que x existe déjà dans cette portée ou dans une portée externe? Sinon, il met une déclaration var x au sommet de cette portée dans la sortie JavaScript.

Supposons maintenant que vous écrivez

 x = 42 

Dans bar.coffee , compilez les deux et concatenez foo.js avec bar.js pour le déploiement. Tu auras

 (function() { var x; x = 'stringy string'; ... }).call(this); (function() { var x; x = 42; ... }).call(this); 

Donc, le x dans foo.coffee et le x en bar.coffee sont totalement isolés l'un de l'autre. Il s'agit d'une partie importante de CoffeeScript: les variables ne se propagent jamais d'un fichier .coffee à un autre, sauf si elles étaient explicitement exportées (en étant attachées à un réseau partagé global ou à exports dans Node.js).

Vous pouvez remplacer ceci en utilisant le drapeau -b ("nu") pour le coffee , mais cela ne devrait être utilisé que dans des cas très spéciaux. Si vous l'avez utilisé avec l'exemple ci-dessus, la sortie que vous obtiendrez serait

 var x; x = 'stringy string'; ... var x; x = 42; ... 

Cela pourrait avoir des conséquences désastreuses. Pour tester cela, essayez d'ajouter setTimeout (-> alert x), 1 dans foo.coffee . Et notez que vous ne devez pas concaténer les deux fichiers JS vous-même si vous utilisez deux balises <script> séparées pour les inclure sur une page, elles fonctionnent toujours comme un seul fichier.

En isolant les étendues de différents modules, le compilateur CoffeeScript vous évite le mal de tête de vous inquiéter si différents fichiers de votre projet peuvent utiliser les mêmes noms de variables locales. C'est une pratique courante dans le monde JavaScript (voir, par exemple, la source jQuery , ou à peu près n'importe quel plugin jQuery) -CoffeeScript prend soin de lui pour vous.

La bonne chose à propos de cette approche est qu'il crée des variables privées, donc il n'y aura aucun conflit avec les noms de variables:

 (function() { var privateVar = 'test'; alert(privateVar); // test })(); alert(typeof privateVar); // undefined 

L'ajout de .call(this) fait que le mot this clé this réfère à la même valeur qu'il a mentionnée en dehors de la fonction. Si elle n'est pas ajoutée, le mot this clé this se référera automatiquement à l'objet global.

Un petit exemple pour montrer la différence suit:

 function coffee(){ this.val = 'test'; this.module = (function(){ return this.val; }).call(this); } var instance = new coffee(); alert(instance.module); // test function coffee(){ this.val = 'test'; this.module = (function(){ return this.val; })(); } var instance = new coffee(); alert(typeof instance.module); // undefined 

C'est une syntaxe similaire à celle-ci:

 (function() { }()); 

Qui s'appelle une fonction immédiate. La fonction est définie et exécutée immédiatement. Les avantages sont que vous pouvez placer tout votre code dans ce bloc et affecter la fonction à une seule variable globale, réduisant ainsi la pollution globale de l'espace de noms. Il offre une bonne portée dans la fonction.

C'est le modèle typique que j'utilise lors de l'écriture d'un module:

 var MY_MODULE = (function() { //local variables var variable1, variable2, _self = {}, etc // public API _self = { someMethod: function () { } } return _self; }()); 

Pas sûr de ce que les inconvénients pourraient être exactement, si quelqu'un d'autre sait de quoi je serais ravi d'en apprendre davantage.