CoffeeScript, Kiedy używać grubej strzałki (=>) nad strzałką (->) i odwrotnie

133

Czy podczas budowania klasy w CoffeeScript cała metoda instancji powinna być zdefiniowana za pomocą =>operatora („gruba strzałka”), a wszystkie metody statyczne należy zdefiniować za pomocą ->operatora?

Ali Salehi
źródło
Czy możesz wysłać przykładowy kod?
sarnold
Zobacz także tę odpowiedź stackoverflow.com/a/17431824/517371
Tobia,

Odpowiedzi:

157

Nie, to nie jest zasada, której bym użył.

Główny przypadek użycia, jaki znalazłem dla grubej strzałki w definiowaniu metod, to sytuacja, gdy chcesz użyć metody jako wywołania zwrotnego, a ta metoda odwołuje się do pól instancji:

class A
  constructor: (@msg) ->
  thin: -> alert @msg
  fat:  => alert @msg

x = new A("yo")
x.thin() #alerts "yo"
x.fat()  #alerts "yo"

fn = (callback) -> callback()

fn(x.thin) #alerts "undefined"
fn(x.fat)  #alerts "yo"
fn(-> x.thin()) #alerts "yo"

Jak widzisz, możesz napotkać problemy z przekazaniem odwołania do metody instancji jako wywołania zwrotnego, jeśli nie używasz fat-arrow. Dzieje się tak, ponieważ gruba strzałka wiąże wystąpienie obiektu, thispodczas gdy cienka strzałka nie, więc metody cienkiej strzałki wywoływane jako wywołania zwrotne, jak powyżej, nie mogą uzyskać dostępu do pól instancji, takich jak @msginne metody instancji. W ostatnim wierszu znajduje się obejście dla przypadków, w których została użyta cienka strzałka.

nicolaskruchten
źródło
2
Co robisz, gdy chcesz użyć tego this, co zostanie wywołane z cienkiej strzałki, ale także zmiennych instancji, które otrzymałeś za pomocą grubej strzałki?
Andrew Mao,
Jak powiedziałem: „W ostatnim wierszu znajduje się obejście dla przypadków, w których zastosowano cienką strzałkę”.
nicolaskruchten
Myślę, że źle zrozumiałeś moje pytanie. Załóżmy, że domyślny zakres wywołania zwrotnego jest thisustawiony na zmienną, której chcę użyć. Jednak chcę również odwołać się do metody klasy, więc chcę thisrównież odwołać się do klasy. Mogę wybrać tylko jedno przypisanie dla this, więc jaki jest najlepszy sposób, aby móc używać obu zmiennych?
Andrew Mao,
@AndrewMao powinieneś raczej zamieścić pełne pytanie na tej stronie, zamiast
prosić
W porządku, pytanie nie jest takie ważne. Ale chciałem tylko wyjaśnić, że to nie było to, o czym mówiłeś w ostatnim wierszu kodu.
Andrew Mao,
13

Kwestią niewymienioną w innych odpowiedziach, o której należy pamiętać, jest to, że wiązanie funkcji grubą strzałką, gdy nie jest to konieczne, może prowadzić do niezamierzonych wyników, takich jak w tym przykładzie z klasą, którą po prostu nazwiemy DummyClass.

class DummyClass
    constructor : () ->
    some_function : () ->
        return "some_function"

    other_function : () =>
        return "other_function"

dummy = new DummyClass()
dummy.some_function() == "some_function"     # true
dummy.other_function() == "other_function"   # true

W tym przypadku funkcje robią dokładnie to, czego można się spodziewać i wydaje się, że użycie grubej strzałki nie przynosi żadnych strat, ale co się dzieje, gdy modyfikujemy prototyp DummyClass po jego już zdefiniowaniu (np. Zmiana jakiegoś alertu lub zmiana wyjścia dziennika) :

DummyClass::some_function = ->
    return "some_new_function"

DummyClass::other_function = ->
    return "other_new_function"

dummy.some_function() == "some_new_function"   # true
dummy.other_function() == "other_new_function" # false
dummy.other_function() == "other_function"     # true

Jak widzimy, nadpisanie naszej wcześniej zdefiniowanej funkcji prototypu powoduje poprawne nadpisanie funkcji some_function, ale funkcja other_function pozostaje taka sama w instancjach, ponieważ gruba strzałka spowodowała powiązanie funkcji other_function z klasy ze wszystkimi instancjami, więc instancje nie będą odwoływać się z powrotem do swojej klasy znaleźć funkcję

DummyClass::other_function = =>
    return "new_other_new_function"

dummy.other_function() == "new_other_new_function"    # false

second_dummy = new DummyClass()
second_dummy.other_function() == "new_other_new_function"   # true

Nawet gruba strzałka nie będzie działać, ponieważ gruba strzałka powoduje jedynie powiązanie funkcji z nowymi instancjami (które zyskują nowe funkcje zgodnie z oczekiwaniami).

Prowadzi to jednak do pewnych problemów, co jeśli potrzebujemy funkcji (np. W przypadku przełączania funkcji logowania do pola wyjściowego lub czegoś podobnego), która będzie działać na wszystkich istniejących instancjach (w tym obsłudze zdarzeń) [jako takiej nie możemy użyć grube strzałki w oryginalnej definicji], ale nadal potrzebujemy dostępu do atrybutów wewnętrznych w module obsługi zdarzeń [dokładny powód, dla którego użyliśmy grubych strzał, a nie cienkich strzał].

Cóż, najprostszym sposobem osiągnięcia tego jest po prostu włączenie dwóch funkcji do oryginalnej definicji klasy, jednej zdefiniowanej cienką strzałką, która wykonuje operacje, które chcesz wykonać, a drugiej zdefiniowanej grubą strzałką, która nie robi nic poza wywołaniem pierwszej funkcji na przykład:

class SomeClass
    constructor : () ->
        @data = 0
    _do_something : () ->
        return @data
    do_something : () =>
        @_do_something()

something = new SomeClass()
something.do_something() == 0     # true
event_handler = something.do_something
event_handler() == 0              # true

SomeClass::_do_something = -> return @data + 1

something.do_something() == 1     # true
event_handler() == 1              # true

Więc kiedy używać cienkich / grubych strzał, można podsumować dość łatwo na cztery sposoby:

  1. Funkcje samej cienkiej strzałki powinny być używane, gdy spełnione są oba warunki:

    • Metoda nigdy nie zostanie przekazana przez referencję, w tym event_handlers, np. Nigdy nie masz przypadku takiego jak: some_reference = some_instance.some_method; some_reference ()
    • AND metoda powinna być uniwersalna we wszystkich instancjach, więc jeśli funkcja prototypu ulegnie zmianie, zmieni się również metoda we wszystkich instancjach
  2. Funkcji samej strzałki tłuszczu należy używać, gdy spełniony jest następujący warunek:

    • Metoda powinna być ściśle powiązana z instancją podczas tworzenia instancji i pozostać trwale związana, nawet jeśli definicja funkcji zmieni się dla prototypu, dotyczy to wszystkich przypadków, w których funkcja powinna być obsługą zdarzeń, a zachowanie funkcji obsługi zdarzeń powinno być spójne
  3. Funkcja grubej strzałki, która bezpośrednio wywołuje funkcję cienkiej strzałki, powinna być używana, gdy spełnione są następujące warunki:

    • Metoda musi być wywoływana przez odwołanie, takie jak program obsługi zdarzeń
    • ORAZ funkcjonalność może ulec zmianie w przyszłości, wpływając na istniejące instancje, zastępując funkcję cienkiej strzałki
  4. Funkcja cienkiej strzałki, która bezpośrednio wywołuje funkcję grubej strzałki (nie pokazano), powinna być używana, gdy spełnione są następujące warunki:

    • Funkcja grubej strzałki musi być zawsze dołączona do instancji
    • ALE funkcja cienkiej strzałki może się zmienić (nawet do nowej funkcji, która nie używa oryginalnej funkcji grubej strzałki)
    • ORAZ funkcja cienkiej strzałki nigdy nie musi być przekazywana przez odniesienie

We wszystkich podejściach należy wziąć pod uwagę w przypadku, gdy funkcje prototypu mogą zostać zmienione, czy zachowanie dla określonych instancji będzie zachowywać się poprawnie, na przykład mimo że funkcja jest zdefiniowana grubą strzałką, jej zachowanie może nie być spójne w instancji, jeśli wywołuje metoda, która została zmieniona w prototypie

Jamesernator
źródło
9

Zwykle ->jest w porządku.

class Foo
  @static:  -> this
  instance: -> this

alert Foo.static() == Foo # true

obj = new Foo()
alert obj.instance() == obj # true

Zwróć uwagę, jak metoda statyczna zwraca obiekt klasy dla, thisa instancja zwraca obiekt instancji dla this.

Dzieje się tak, że składnia wywołania dostarcza wartości this. W tym kodzie:

foo.bar()

foobędzie bar()domyślnie kontekstem funkcji. Więc to po prostu działa tak, jak chcesz. Gruba strzałka jest potrzebna tylko wtedy, gdy wywołujesz te funkcje w inny sposób, który nie używa składni kropki do wywołania.

# Pass in a function reference to be called later
# Then later, its called without the dot syntax, causing `this` to be lost
setTimeout foo.bar, 1000

# Breaking off a function reference will lose it's `this` too.
fn = foo.bar
fn()

W obu tych przypadkach użycie grubej strzałki do zadeklarowania, że ​​funkcja pozwoli im działać. Ale jeśli nie robisz czegoś dziwnego, zwykle nie musisz.

Więc używaj, ->dopóki naprawdę nie potrzebujesz =>i nigdy nie używaj =>domyślnie.

Alex Wayne
źródło
1
To się nie powiedzie, jeśli:x = obj.instance; alert x() == obj # false!
nicolaskruchten
2
Oczywiście, że tak, ale oznaczałoby to „robienie tego źle”. Teraz zredagowałem moją odpowiedź i wyjaśniłem, kiedy =>byłoby potrzebne w metodach statycznych / instancji klasy.
Alex Wayne
Nitpick: // is not a CoffeeScript commentpodczas gdy # is a CoffeeScript comment.
nicolaskruchten
Jak to się setTimeout foo.bar, 1000„robi źle”? Używanie grubej strzały jest znacznie przyjemniejsze niż używanie setTimeout (-> foo.bar()), 1000IMHO.
nicolaskruchten
1
@nicolaskruchten Jest oczywiście argument za taką składnią setTimeout. Ale twój pierwszy komentarz jest nieco wymyślony i nie ujawnia uzasadnionego przypadku użycia, ale po prostu ujawnia, jak może się zepsuć. Po prostu mówię, że nie należy używać a, =>chyba że jest to potrzebne z dobrego powodu, szczególnie w przypadku metod instancji klas, w których wiąże się to z kosztem wydajności związanym z utworzeniem nowej funkcji, która musi być związana z instancją.
Alex Wayne,
5

tylko przykład zrozumienia grubej strzały

nie działa: (@canvas undefined)

class Test
  constructor: ->
    @canvas = document.createElement 'canvas'
    window.addEventListener 'resize', ->
      @canvas.width = window.innerWidth
      @canvas.height = window.innerHeight

prace: (zdefiniowano @canvas)

class Test
  constructor: ->
    @canvas = document.createElement 'canvas'
    window.addEventListener 'resize', =>
      @canvas.width = window.innerWidth
      @canvas.height = window.innerHeight
user3896501
źródło