Jak zdefiniować zmienne globalne w CoffeeScript?

317

Na Coffeescript.org:

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

bawbag(5, 10) 

skompilowałby się w:

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

kompilacja za pomocą skryptu kawy pod opakowaniami node.js, które:

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

Dokumenty mówią:

Jeśli chcesz utworzyć zmienne najwyższego poziomu do użycia dla innych skryptów, dołącz je jako właściwości w oknie lub w obiekcie eksportu w CommonJS. Operator egzystencjalny (omówiony poniżej) daje niezawodny sposób, aby dowiedzieć się, gdzie je dodać, jeśli celujesz zarówno w CommonJS, jak i przeglądarkę: root = eksportuje? to

Jak zdefiniować zmienne globalne w CoffeeScript. Co oznacza „dołącz je jako właściwości do okna”?

Handloomweaver
źródło
4
Zauważ, że używanie zmiennych globalnych jest złe, c2.com/cgi/wiki?GlobalVariablesAreBad , a nawet uważane za szkodliwe, c2.com/cgi/wiki?GotoConsoveredHarmful . I naprawdę nie ma żadnego powodu, aby używać ich w JavaScript, ponieważ masz świetne funkcje, takie jak zamknięcia, które mogą rozwiązać większość problemów, których używasz do zmiennych globalnych.
Evgeny
9
@ Evgeny Chociaż zgadzam się z tobą tutaj, w niektórych przypadkach konieczne jest utworzenie centralnego obiektu „aplikacji” i dołączenie do niego modułów.
jackyalcine
1
obiekty centralne można zapisać w istniejących obiektach stanu globalnego, takich jak windowobiekt lub exportsobiekt. nie ma potrzeby tworzenia zmiennych globalnych.
Evgeny,
9
@ Zmienne globalne Evgeny są zapisywane jako właściwości obiektu window(lub globalnodejs)
shesek
21
Tak, nie jest wcale tak źle mieć globalny var. Po prostu zła praktyka bezmyślnego łączenia aplikacji z nimi. Zadeklarowanie jednego i użycie go jako fabryki adapterów, takiej jak jQuery lub jakaś przestrzeń nazw, jest bardzo powszechną praktyką.
Erik Reppen

Odpowiedzi:

419

Ponieważ skrypt kawy nie ma varinstrukcji, automatycznie wstawia ją do wszystkich zmiennych w skrypcie kawy, w ten sposób zapobiega wyciekaniu skompilowanej wersji JavaScript do globalnej przestrzeni nazw .

Ponieważ więc nie ma sposobu, aby coś „wyciekło” do globalnej przestrzeni nazw od strony skryptów kawy celowo, musisz zdefiniować swoje zmienne globalne jako właściwości obiektu globalnego .

dołącz je jako właściwości do okna

Oznacza to, że trzeba zrobić coś podobnego window.foo = 'baz';, który obsługuje sprawy przeglądarki, ponieważ tam globalny obiekt jest window.

Node.js

W Node.js nie ma windowobiektu, zamiast tego jest exportsobiekt przekazywany do opakowania, które otacza moduł Node.js (patrz: https://github.com/ry/node/blob/master/src/node.js# L321 ), więc w Node.js musisz zrobić exports.foo = 'baz';.

Teraz rzućmy okiem na to, co mówi w cytacie z dokumentów:

... atakując zarówno CommonJS, jak i przeglądarkę: root = eksportuje? to

Jest to oczywiście skrypt do kawy, więc przyjrzyjmy się temu, co to właściwie kompiluje:

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

Najpierw sprawdzi, czy exportsjest zdefiniowany, ponieważ próba odwołania się do nieistniejącej zmiennej w JavaScript dałaby w przeciwnym razie błąd SyntaxError (z wyjątkiem sytuacji, gdy jest używana z typeof)

Więc jeśli exportsistnieje, co ma miejsce w Node.js (lub w źle napisanej witrynie WebSite ...), root wskaże exports, w przeciwnym razie this. Co to jest this?

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

Użycie .callna funkcji spowoduje powiązanie thiswnętrza funkcji z pierwszym przekazanym parametrem, w przypadku przeglądarki thisbędzie teraz windowobiektem, w przypadku Node.js globalnym kontekstem, który jest również dostępny jako globalobiekt.

Ale ponieważ masz requirefunkcję w Node.js, nie ma potrzeby przypisywania czegoś do globalobiektu w Node.js, zamiast tego przypisujesz exportsobiekt, który następnie jest zwracany przez requirefunkcję.

Kawa-skrypt

Po tych wszystkich wyjaśnieniach oto, co musisz zrobić:

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

Spowoduje to zadeklarowanie naszej funkcji foow globalnej przestrzeni nazw (cokolwiek się stanie).
To wszystko :)

Ivo Wetzel
źródło
1
@IvoWetzel - Jaka jest różnica pomiędzy global, GLOBALi rootobiektów w node.js?
Aadit M Shah,
1
próba odwołania się do nieistniejącej zmiennej w JavaScript dałaby w przeciwnym razie błąd SyntaxError Nie masz na myśli ReferenceError?
alex
12
Lub jeszcze krócej:(exports ? this).foo = -> 'Hello World'
Dane O'Connor,
3
this.foo jest często! = window.foo, jeśli jednak „ten” kontekst jest już obiektem. Jest to myląca składnia.
Kevin
1
Chociaż zgadzam się na używanie global = exports ? this. Twierdzenie, że „w przypadku Node.js byłby to kontekst globalny ...” jest błędne, ponieważ thiszmienna, gdy jest wymagana lub uruchamiana przez node.js, jest oceniana jako zakres modułu. Więc jeśli spodziewasz się, że ustawienie rekwizytów sprawi, że będzie on dostępny na całym świecie, będziesz rozczarowany. Jeśli chcesz ustawić rzeczy globalnie w kontekście node.js, musisz globalzamiast tego użyć zmiennej this.
KFL
58

Wydaje mi się, że @atomicules ma najprostszą odpowiedź, ale myślę, że można ją trochę uprościć. Musisz postawić @przed wszystkim, co chcesz być globalne, aby kompilowało się this.anythingi thisodnosiło do obiektu globalnego.

więc...

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

bawbag(5, 10)

kompiluje się do ...

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

i działa wewnątrz i na zewnątrz opakowania podanego przez 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
źródło
7
Ale to nie zadziała, jeśli jesteś już w innym zasięgu, prawda? Ponieważ wtedy thisnie odnosi się już do obiektu globalnego
Sherwin Yu,
1
To prawda, więc możesz albo zdefiniować zmienną w odpowiednim zakresie (i użyć jej w innych), albo zdefiniować jako, window.myVariablektóra będzie działać w dowolnym miejscu.
Billy Moon,
2
Nie musisz definiować innej zmiennej, po prostu użyj =>zamiast tego ->, instruuje coffeescript, aby utworzyć funkcję w tej / globalnej przestrzeni nazw
Ricardo Villamil
2
to było bardzo pomocne, teraz mogę tworzyć globalne obiekty i funkcje w osobnym skrypcie do kawy
Diego Fernando Murillo Valenci
To jest o wiele lepsze. Przeniesienie JS do CS wymagało ode mnie zmiany wielu wywołań funkcji w celu użycia obiektu okna, teraz mogę to cofnąć
casraf
33

Ivo go przybił, ale wspomnę, że jest jedna brudna sztuczka, której można użyć, chociaż nie polecam jej, jeśli chcesz zdobyć punkty stylu: możesz osadzić kod JavaScript bezpośrednio w CoffeeScript, unikając go za pomocą backsticks.

Oto dlaczego jest to zwykle zły pomysł: kompilator CoffeeScript nie zna tych zmiennych, co oznacza, że ​​nie będą przestrzegać normalnych zasad określania zakresu CoffeeScript. Więc,

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

kompiluje się do

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

a teraz masz dwie foos w różnych zakresach. Nie ma sposobu, aby zmodyfikować kod globalny foo z kodu CoffeeScript bez odwoływania się do obiektu globalnego, jak opisano w Ivy.

Oczywiście jest to problem tylko wtedy, gdy wykonasz zadanie foow CoffeeScript - jeśli stałoby się tylko do fooodczytu po otrzymaniu jego początkowej wartości (tj. Jest stałą globalną), wówczas podejście do osadzonego rozwiązania JavaScript może być trochę akceptowalne (choć nadal Niepolecane).

Trevor Burnham
źródło
1
To było dla mnie przydatne rozwiązanie, ponieważ używam Titanium z CoffeeScript. Eksport i obiekt okna są tam niedostępne.
Pier-Olivier Thibault,
W rzeczywistości jest to tylko jedna foozmienna lokalna z powodu varpodnoszenia (JS skanuje wszystkie vardeklaracje i interpretuje je tak, jakby znajdowały się na szczycie funkcji)
Kornel
@porneL Masz rację; Wybrałem zły przykład. Chodzi o to, że kompilator CoffeeScript nie wykonuje żadnej analizy kodu JavaScript, który uciekł w tył, dzięki czemu można uzyskać nieparzyste dane wyjściowe.
Trevor Burnham,
2
@ Pier-OlivierThibault Jeśli chcesz używać Globali w Tytanie, możesz użyć Ti.App.myGlobalVar = „ImAGlobalVar” i nie potrzebujesz backticków
Jakob Lnr
to jest poprawna odpowiedź, przynajmniej dla Node.js. robi expect = require('chai').expect;czyni expectzmienną dostępną we wszystkich moich plikach testowych!
pocesar
11

Możesz przekazać opcję -b podczas kompilowania kodu za pomocą skryptu do kawy w pliku node.js. Skompilowany kod będzie taki sam jak na coffeescript.org.

phongnh
źródło
W jaki sposób? Gdzie umieścić opcję -b?
Harry,
1
@Harry - -b/ --bareidzie bezpośrednio po coffeepoleceniu.
ocodo
9

Aby dodać do odpowiedzi Ivo Wetzela

Wydaje się, że istnieje skrócona składnia exports ? this, którą mogę znaleźć tylko w dokumentach grupy Google .

Tj. Na stronie internetowej, aby udostępnić funkcję globalnie, deklarujesz ją ponownie z @prefiksem:

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

<a href="javascript:aglobalfunction()" >Click me!</a>
atomów
źródło
9
„@” W @aglobalfunction jest po prostu zastąpione przez „to.”, Więc kompiluje się do „this.aglobalfunction”. Działa to, ponieważ zakres funkcji opakowania coffeescript (jeśli jest zastosowany) jest zasięgiem globalnym.
Chris,
9

Myślę, że to, co próbujesz osiągnąć, można po prostu zrobić w następujący sposób:

Podczas kompilowania skryptu coffeescript użyj parametru „-b”.

-b/ --bare Skompiluj JavaScript bez opakowania bezpieczeństwa funkcji najwyższego poziomu.

Więc coś takiego: coffee -b --compile somefile.coffee whatever.js

Spowoduje to wygenerowanie kodu tak jak w witrynie CoffeeScript.org.

Sankalp Singha
źródło
7

Jeśli jesteś złym człowiekiem (ja jestem złym człowiekiem), możesz uzyskać tak proste: (->@)()

Jak w,

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

To działa, ponieważ kiedy Wywoływanie ReferenceDo Function„gołe” (czyli func()zamiast new func()lub obj.func()), coś powszechnie określane jako „funkcja wywołania wywołania wzór”, zawsze wiąże thisdo globalnego obiektu dla tego kontekstu wykonania .

Powyższy CoffeeScript po prostu się kompiluje (function(){ return this })(); dlatego ćwiczymy to zachowanie, aby niezawodnie uzyskać dostęp do obiektu globalnego.

ELLIOTTCABLE
źródło
To jest genialne!
metalim
Jedyne, co dla mnie zadziałało. Hate CoffeeScript.
pcv
Uwielbiam CoffeeScript. Jak dotąd jest to najlepszy dostępny język programowania. Szkoda, że ​​został stworzony i utrzymany jako projekt hobby, co prowadzi do chaosu i głupoty w jego wzorcach użytkowania.
metalim
3

Ponieważ coffeescript jest rzadko używany samodzielnie, możesz użyć globalzmiennej dostarczonej przez node.js lub browserify (i dowolnych potomków, takich jak coffeeify, skrypty budowania gula itp.).

W node.js globalznajduje się globalna przestrzeń nazw.

W Browserify globaljest równy window.

Więc tylko:

somefunc = ->
  global.variable = 123
metalim
źródło