Comment définir des variables globales dans CoffeeScript?

317

Sur Coffeescript.org:

bawbag = (x, y) ->
    z = (x * y)

bawbag(5, 10) 

compilerait pour:

var bawbag;
bawbag = function(x, y) {
  var z;
  return (z = (x * y));
};
bawbag(5, 10);

la compilation via coffee-script sous node.js enveloppe ainsi:

(function() {
  var bawbag;
  bawbag = function(x, y) {
    var z;
    return (z = (x * y));
  };
  bawbag(5, 10);
}).call(this);

Les documents disent:

Si vous souhaitez créer des variables de niveau supérieur pour d'autres scripts à utiliser, attachez-les en tant que propriétés sur la fenêtre ou sur l'objet exports dans CommonJS. L'opérateur existentiel (décrit ci-dessous) vous donne un moyen fiable de savoir où les ajouter, si vous ciblez à la fois CommonJS et le navigateur: root = exports? ce

Comment puis-je définir des variables globales dans CoffeeScript. Que signifie «les attacher en tant que propriétés sur la fenêtre»?

Handloomweaver
la source
4
Notez que l'utilisation de variables globales est mauvaise, c2.com/cgi/wiki?GlobalVariablesAreBad , et même considérée comme nuisible, c2.com/cgi/wiki?GotoConsideredHarmful . Et il n'y a vraiment aucune raison de les utiliser en JavaScript, car vous disposez d'excellentes fonctionnalités telles que les fermetures qui peuvent résoudre la plupart des problèmes que vous utilisez pour résoudre des variables globales.
Evgeny
9
@Evgeny Bien que je sois d'accord avec vous ici, dans certains cas, il est nécessaire de créer un objet «application» central et d'y attacher des modules.
jackyalcine
1
les objets centraux peuvent être enregistrés dans des objets d'état global existants, comme l' windowobjet ou l' exportsobjet. pas besoin de créer de variables globales.
Evgeny
9
Les variables globales @Evgeny sont enregistrées en tant que propriétés de l'objet window(ou globalsur nodejs)
shesek
21
Ouais, ce n'est pas mal du tout d'avoir une var globale. Juste une mauvaise pratique pour utiliser votre application avec eux sans réfléchir. En déclarer un et l'utiliser comme fabrique d'adaptateurs comme jQuery ou une sorte d'espace de noms est une pratique très courante.
Erik Reppen

Réponses:

419

Étant donné que le script coffee n'a pas de vardéclaration, il l'insère automatiquement pour toutes les variables dans le script coffee, de cette façon, il empêche la version JavaScript compilée de tout divulguer dans l' espace de noms global .

Donc, puisqu'il n'y a aucun moyen de faire quelque chose "fuir" dans l' espace de noms global du côté café-script des choses, vous devez définir vos variables globales en tant que propriétés de l' objet global .

les attacher en tant que propriétés sur la fenêtre

Cela signifie que vous devez faire quelque chose comme window.foo = 'baz';, qui gère le cas du navigateur, car l' objet global est le window.

Node.js

Dans Node.js, il n'y a pas d' windowobjet, mais plutôt l' exportsobjet qui est passé dans le wrapper qui enveloppe le module Node.js (Voir: https://github.com/ry/node/blob/master/src/node.js# L321 ), donc dans Node.js ce que vous devez faire est exports.foo = 'baz';.

Voyons maintenant ce qu'il indique dans votre citation de la documentation:

... ciblant à la fois CommonJS et le navigateur: root = exports? ce

Ceci est évidemment un script de café, alors regardons ce que cela compile réellement:

var root;
root = (typeof exports !== "undefined" && exports !== null) ? exports : this;

Tout d'abord, il vérifiera si exportsest défini, car essayer de référencer une variable inexistante en JavaScript produirait sinon une SyntaxError (sauf lorsqu'elle est utilisée avectypeof )

Donc, s'il exportsexiste, ce qui est le cas dans Node.js (ou dans un site Web mal écrit ...), la racine pointera exports, sinon this. Alors quoi this?

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

L'utilisation .calld'une fonction liera l' thisintérieur de la fonction au premier paramètre passé, dans le cas où le navigateur thisserait maintenant l' windowobjet, dans le cas de Node.js, ce serait le contexte global qui est également disponible en tant globalqu'objet.

Mais puisque vous avez la requirefonction dans Node.js, il n'est pas nécessaire d'affecter quelque chose à l' globalobjet dans Node.js, à la place vous attribuez à l' exportsobjet qui est ensuite renvoyé par la requirefonction.

Café-Script

Après toutes ces explications, voici ce que vous devez faire:

root = exports ? this
root.foo = -> 'Hello World'

Cela déclarera notre fonction foodans l'espace de noms global (quel qu'il soit).
C'est tout :)

Ivo Wetzel
la source
1
@IvoWetzel - Quelle est la différence entre les global, GLOBALet les rootobjets Node.js?
Aadit M Shah
1
essayer de référencer une variable inexistante en JavaScript donnerait sinon une SyntaxError. Vous ne voulez pas dire ReferenceError?
alex
12
Ou encore plus court:(exports ? this).foo = -> 'Hello World'
Dane O'Connor
3
this.foo est souvent! = window.foo si vous êtes «ce» contexte est déjà un objet. Il s'agit d'une syntaxe déroutante.
Kevin
1
Bien que je sois d'accord avec l'utilisation global = exports ? this. L'affirmation selon laquelle "dans le cas de Node.js ce serait le contexte global ..." est erronée car la thisvariable, lorsqu'elle est requise ou exécutée par node.js, est évaluée comme la portée du module. Donc, si vous vous attendez à ce que les accessoires le rendent accessible à l'échelle mondiale, vous serez déçu. Si vous souhaitez définir les choses globalement dans le contexte node.js, vous devez utiliser la globalvariable plutôt que this.
KFL
58

Il me semble que @atomicules a la réponse la plus simple, mais je pense qu'elle peut être simplifiée un peu plus. Vous devez mettre un @avant tout ce que vous voulez être global, afin qu'il se compile this.anythinget fasse thisréférence à l'objet global.

alors...

@bawbag = (x, y) ->
    z = (x * y)

bawbag(5, 10)

compile pour ...

this.bawbag = function(x, y) {
  var z;
  return z = x * y;
};
bawbag(5, 10);

et fonctionne à l'intérieur et à l'extérieur du wrapper donné par node.js

(function() {
    this.bawbag = function(x, y) {
      var z;
      return z = x * y;
    };
    console.log(bawbag(5,13)) // works here
}).call(this);

console.log(bawbag(5,11)) // works here
Billy Moon
la source
7
Mais cela ne fonctionnera pas si vous êtes déjà dans une autre portée, non? Parce thisqu'alors ne fait plus référence à l'objet global
Sherwin Yu
1
C'est correct, vous pouvez donc soit définir votre variable dans une portée appropriée (et l'utiliser dans d'autres), soit définir celle window.myVariablequi fonctionnera n'importe où.
Billy Moon
2
Vous n'avez pas besoin de définir une autre variable, il suffit d'utiliser à la =>place de ->celle qui indique à coffeescript de créer la fonction sous l'espace de noms this / global
Ricardo Villamil
2
c'était si utile, maintenant je peux créer des objets et des fonctions globales dans un script de café séparé
Diego Fernando Murillo Valenci
C'est bien mieux. Le transfert de JS vers CS avait besoin de moi pour changer beaucoup d'appels de fonction pour utiliser l'objet fenêtre, maintenant je peux revenir sur cela
casraf
33

Ivo l'a cloué, mais je mentionnerai qu'il y a une sale astuce que vous pouvez utiliser, bien que je ne le recommande pas si vous recherchez des points de style: vous pouvez incorporer du code JavaScript directement dans votre CoffeeScript en l'échappant avec des astuces.

Cependant, voici pourquoi il s'agit généralement d'une mauvaise idée: le compilateur CoffeeScript ne connaît pas ces variables, ce qui signifie qu'elles n'obéiront pas aux règles de cadrage CoffeeScript normales. Alors,

`foo = 'bar'`
foo = 'something else'

compile en

foo = 'bar';
var foo = 'something else';

et maintenant vous avez deux foos dans des portées différentes. Il n'y a aucun moyen de modifier le global à foo partir du code CoffeeScript sans référencer l'objet global, comme Ivy l'a décrit.

Bien sûr, cela n'est un problème que si vous effectuez une affectation foodans CoffeeScript - si vous foodevenez en lecture seule après avoir reçu sa valeur initiale (c'est-à-dire qu'il s'agit d'une constante globale), alors l'approche de la solution JavaScript intégrée peut être un peu acceptable (bien que toujours non recommandé).

Trevor Burnham
la source
1
C'était une solution utile pour moi car j'utilise Titanium avec CoffeeScript. Les exportations et les objets de fenêtre y sont inexistants.
Pier-Olivier Thibault
En fait, ce n'est qu'une foovariable locale , en raison du varlevage (JS recherche toutes les vardéclarations et les interprète comme si elles étaient au sommet de la fonction)
Kornel
@porneL Vous avez raison; J'ai choisi un mauvais exemple. Le fait est que le compilateur CoffeeScript n'effectue aucune analyse du code JavaScript échappé en backtick, vous pouvez donc obtenir une sortie impaire.
Trevor Burnham
2
@ Pier-OlivierThibault Si vous souhaitez utiliser Globals in Titanium, vous pouvez utiliser Ti.App.myGlobalVar = "ImAGlobalVar" et ne pas avoir besoin de backticks
Jakob Lnr
c'est la bonne réponse, pour Node.js au moins. faire expect = require('chai').expect;rend la expectvariable disponible dans tous mes fichiers de test!
pocesar
11

Vous pouvez passer l'option -b lorsque vous compilez du code via coffee-script sous node.js. Le code compilé sera le même que sur coffeescript.org.

phongnh
la source
Comment? Où dois-je mettre l'option -b?
Harry
1
@Harry - -b/ --bareva directement après lecoffee commande.
ocodo
9

Pour ajouter à la réponse d'Ivo Wetzel

Il semble y avoir une syntaxe abrégée pour exports ? thislaquelle je ne peux trouver que documenté / mentionné sur une publication de groupe Google .

C'est-à-dire que dans une page Web pour rendre une fonction disponible globalement, vous déclarez à nouveau la fonction avec un @préfixe:

<script type="text/coffeescript">
    @aglobalfunction = aglobalfunction = () ->
         alert "Hello!"
</script>

<a href="javascript:aglobalfunction()" >Click me!</a>
atomicules
la source
9
Le «@» dans @aglobalfunction est simplement remplacé par «this.», Donc en compilant «this.aglobalfunction». Cela fonctionne car la portée de la fonction d'encapsulage coffeescript (si elle est appliquée) est la portée globale.
Chris
9

Je pense que ce que vous essayez de réaliser peut simplement être fait comme ceci:

Pendant que vous compilez le coffeescript, utilisez le paramètre "-b".

-b/ --bare Compilez le JavaScript sans le wrapper de sécurité de fonction de niveau supérieur.

Donc quelque chose comme ça: coffee -b --compile somefile.coffee whatever.js

Cela affichera votre code comme sur le site CoffeeScript.org.

Sankalp Singha
la source
7

Si vous êtes une mauvaise personne (je suis une mauvaise personne), vous pouvez être aussi simple que cela: (->@)()

Un péché,

(->@)().im_a_terrible_programmer = yes
console.log im_a_terrible_programmer

Cela fonctionne, car lorsque vous invoquez un Referencevers un Function'nu' (c'est-à-dire func(), au lieu de new func()ou obj.func()), quelque chose communément appelé le 'modèle d'invocation d'appel de fonction', se lie toujoursthis à l'objet global pour ce contexte d'exécution .

Le CoffeeScript ci-dessus se compile simplement en (function(){ return this })(); nous exerçons donc ce comportement pour accéder de manière fiable à l'objet global.

ELLIOTTCABLE
la source
C'est génial!
metalim
La seule chose qui a fonctionné pour moi. Déteste CoffeeScript.
pcv
J'adore CoffeeScript. Jusqu'à présent, c'est le meilleur langage de programmation. Dommage qu'il ait été créé et maintenu comme un projet de loisir, conduisant au chaos et à la stupidité dans ses modèles d'utilisation.
metalim
3

Comme coffeescript est rarement utilisé seul, vous pouvez utiliser une globalvariable fournie par node.js ou browserify (et tout descendant comme coffeeify, gulp build scripts, etc.).

Dans node.js globalest l'espace de noms global.

Dans browserify globalest égal à window.

Donc, juste:

somefunc = ->
  global.variable = 123
metalim
la source