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?
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?
Odpowiedzi:
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, this
podczas 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 @msg
inne metody instancji. W ostatnim wierszu znajduje się obejście dla przypadków, w których została użyta cienka strzałka.
this
, co zostanie wywołane z cienkiej strzałki, ale także zmiennych instancji, które otrzymałeś za pomocą grubej strzałki?
this
ustawiony na zmienną, której chcę użyć. Jednak chcę również odwołać się do metody klasy, więc chcę this
ró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?
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:
Funkcje samej cienkiej strzałki powinny być używane, gdy spełnione są oba warunki:
Funkcji samej strzałki tłuszczu należy używać, gdy spełniony jest następujący warunek:
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:
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:
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
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, this
a instancja zwraca obiekt instancji dla this
.
Dzieje się tak, że składnia wywołania dostarcza wartości this
. W tym kodzie:
foo.bar()
foo
bę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.
x = obj.instance; alert x() == obj # false!
=>
byłoby potrzebne w metodach statycznych / instancji klasy.
// is not a CoffeeScript comment
podczas gdy # is a CoffeeScript comment
.
setTimeout foo.bar, 1000
„robi źle”? Używanie grubej strzały jest znacznie przyjemniejsze niż używanie setTimeout (-> foo.bar()), 1000
IMHO.
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ą.
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